Search for a string in an infinite stream of input string.
问题,read()从stream中读出一个字符。
然后在stream 中找到含有目标字符串的位置。
这个题目是个典型的KMP算法。
KMP算法的关键在于如果build Next数组
讲KMP算法讲的比较好的网站
http://www.ruanyifeng.com/blog/2013/05/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm.html
其实用平实的逻辑思考也能想明白这个算法究竟是怎么来。
string S1 abcd......
string S2 Ai.....An
1. 当拿S2和S1去匹配的时候,当在S2的第K位置发生不匹配的时候,最最naive的方法就是直接跳回到第二个字符再比。注意至少S2中,0~k-1位置上的内容和前面是匹配的。
2. 当字符中出现重复的时候,特别是后缀和前缀有匹配的时候, 比如在ABCDABD ‘D’出现不匹配的时候,但是在不匹配的位置之前都是匹配的。
那么以其字符串移到'B'CDAB B处进行匹配
还不如直接看看发生错配处前面的后缀,与字符串的前缀的重合长度,如果重合长度为1,那就直接从第二位比起吧。
3. 那就对要搜索的字符串,先假设在各个位置发生不匹配的时候最长匹配的字符串长度记到表格里。next
4. 那下次发生不匹配的时候直接滑动到那个位置就可以了。
拿下面这个例子来说,在不匹配的时候 next[B]的位置是2, 那就直接从第三个字符开始比就行了。
这个就是整个KMP算法的思路和一般暴力搜索的区别。而且这个思路可以扩展到2维,比如图像匹配这种问题。
那如何来建这个表呢?
根据前面的描述,最笨的办法就是,直接到一个位置去试前缀和后缀的长度,然后去检验。。。。
但是呢,这个可以用一维动态规划来做,这也是为啥,这个next这么难搞了。
next[0]= 0
递推式
next[j] =next[j-1]+1 if (A[next[j-1] == A[j])
or next[j]=0 (if(A[next[j-1] != A[j] ) 1(if(A[0] == A[j])
void buildTable(char *x, int length, int* next) { int i, j; next[0] = 0; for (int i = 1, j = 0; i<length; i++) { while (j>0 && x[i] != x[j]) { j = next[j - 1]; } if (x[i] == x[j]) { j++; } next[i] = j; } }
改成这样,就容易理解多了
void buildtable(char *x, int length, int* next) { next[0] = 0; for (int j = 1; j < length; j++) { if (x[next[j - 1]] == x[j]) { next[j] = next[j - 1] + 1; } else { if (x[0] == x[j]) { next[j] = 1; } else next[j] = 0; } } }
这个从stream里面读要比直接比两个字符串更加可恶,因为你要考虑好啥时候该读,在什么地方读。。
一种是一直在首字符串不匹配的情况下,那就需要读,
一种是前面一次比较是匹配的情况下,那就需要读。其他的好理解了。
int find(char* str) { int len = strlen(str); int counter = 0; char ch; if (str == NULL) return -1; int *next = (int*)malloc(strlen(str)); buildTable(str, strlen(str), next); bool found = false; bool stillmatch = true; int s_index = 0; while (!found) { if (stillmatch ||s_index == 0 ) { ch = read(); counter++; } if (str[s_index] == ch) {//read char match stillmatch = true; //need read from stream s_index++; // read compare next char if (s_index == len){ //end of search string found = true; break; } } else //not match { stillmatch = false; if (s_index > 0) //we already found some matches { s_index = next[s_index - 1]; //backtrack } } } if (next != NULL) free(next); if (found) { return counter - len; } else { return -1; } }
另外一种实现,很是巧妙但是可读性。。。。
public static int[] next; public static boolean kmp(String str, String dest) { // i stands for index of str string, j stands for index in dest string. // At the beginning of each loop process, j is the new position of dest // taht should be compared. for (int i = 0, j = 0; i < str.length(); i++) { while (j > 0 && str.charAt(i) != dest.charAt(j)) // 遇到不匹配的情况,就回退到nextval 标定的值 j = next[j - 1]; if (str.charAt(i) == dest.charAt(j)) j++;
//匹配,继续往前搜 if (j == dest.length()) return true; } return false; } public static int[] kmpNext(String str) { int[] next = new int[str.length()]; next[0] = 0; // i stands for index of string, j is temporary for particail match // values computing, at the beginning of each loop process, j is the // particial match value of former character . for (int i = 1, j = 0; i < str.length(); ++i) { while (j > 0 && str.charAt(i) != str.charAt(j)) j = next[j - 1];// j will be recomputed in the recursion. Take // care that next[j-1] is the particial match // value of the first j characters substirng. if (str.charAt(i) == str.charAt(j)) // If not in this case, j must // meets end, equals to zero. ++j; next[i] = j; } return next; }
总结:
KMP算法
1. 了解暴力解法怎么解,无论是内存随意读,还是从流读。
2. 理解为啥需要建这个next表,这个next查找表的每个位置的值究竟啥意义。这个思路如何拓展到二维。
3. 如果建这个表。一维动态规划
4. 如果从流读,什么条件的情况下需要读。