我们经常会有这种需求,比如在字符串goodgoogle
中去查找google
这个子串,这种子串的定位操作通常称作串的模式匹配。
朴素的模式匹配算法
对于以上问题,最容易想到的方式当然是暴力破解了,我们可能写出如下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| #include <stdio.h> #include <string.h> int getIndex (const char *pStr,const char *cStr) { int pLen = strlen(pStr); int cLen = strlen(cStr); int pos = -1,i = 0,j = 0; while (i < pLen && j < cLen) { if (pStr[i] == cStr[j]) { i ++; j ++; } else { i = i - j + 1; j = 0; } } if (j = cLen) { pos = i - cLen; } return pos; } int main () { char *p = "goodgoodgoodgoogle"; char *c = "google"; int pos; pos = getIndex(p, c); printf("pos: %d\n", pos); return 0; }
|
以上算法,简单地说,就是对依次用主串的每一个字符作为开头,与子串字符进行匹配。主串做大循环,子串做小循环,直到匹配成功为止。
它的时间复杂度为O(n + m)
,其中n为主串长度,m为子串长度。
KMP算法
朴素的模式匹配算法是低效的,于是三位前辈——D.E.Knuth、J.H.Morris、V.R.Pratt就研究出了KMP算法,有人称看毛片算法。
KMP算法是一个模式匹配算法,可以大大避免重复遍历的情况。
它具有以下特点:
- 主串的i值不回溯。
- 子串的j值会在不匹配时发生回溯,回溯的位置为next[j]。
- next数组表示:在子串与主串在某处失配时,子串j应该回溯的位置,即next[j]。
- 而next[j]的值取决与子串位置j之前的字符串中,前后缀的相似度。
下面一张图辅助理解:
next数组求解
KMP算法的核心就是next数组,有了一个子串的next数组,KMP算法也就很容易实现了!
那么给定一个子串,如何求出其next数组呢?
要解决这个问题,我们需要谨记next数组的相关概念:
它的长度和子串长度相同,它的每个值说明了在子串和主串失配时,应该从子串的哪个位置开始下次匹配。而next[j]的值,就是子串在j位置之前的字符串的前后缀相似度。
它的求解过程可以图解如下:
代码实现
有了next数组,就可以上代码啦,代码虽然写的不太简洁,但是问题可以解决了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| #include <stdio.h> #include <string.h> int * getNext (const char *c) { int cLen = strlen(c); int next[cLen]; int *p = next; int i = 0; int j = 1; next[0] = -1; while (j < cLen) { if (c[i] == c[j-1]) { next[j] = next[j-1] + 1; i ++; j ++; } else { i --; if (i < 0) { next[j] = 0; i ++; j ++; } } } return p; } int getIndex (const char *p, const char *c) { int pLen = strlen(p); int cLen = strlen(c); int pos = -1, i = 0, j = 0; int *next = getNext(c); while (i < pLen && j < cLen) { if (j == -1 || p[i] == c[j]) { i ++; j ++; } else { j = next[j]; } } if (j == cLen) { pos = i - cLen; } return pos; } int main () { char *p = "aaaaabcdefaaaaax"; char *c = "aaaaax"; int pos; pos = getIndex(p, c); printf("pos: %d\n", pos); return 0; }
|
以上就是完整的KMP算法的实现,不过KMP算法还有可以优化的地方,后续补充。