1.背景:KMP是由3个外国人想出来的设计的线性时间字符串匹配算法。时间复杂度很低O(n),是判定字串的一个十分简便的方法。
2.运算步骤:假定1个字符串A,对字符串A匹配A的子串,求出一个数组next,通过预算减少了运算的时间,其中next[i]表示了A中以i结尾的的非前缀字串,非前缀字串很好理解的,就比如bread的前缀字串有b,br,bre,brea;(如果字符串A为a,那么A的字串为空)然后将以i为结尾的非前缀字串与A的前缀能匹配的最大长度,就是next[i]=max{j},其中i<j且A[i-j+1~j]=A[1~j];然后对A与B2个字符串相匹配,求出数组f,其中f[i]表示B中以i结尾的字串与A的前缀能够匹配的最大长度,就是f[i]=max{j},其中j≤i且B[i-j+1~i]=A[1~j];这样对字串进行优化。
3.KMP是如何提高计算的速度的?
这个十分容易理解,比如给一个字符串A,里面是abcabcabccc,用另一个串B去比较,假设B为abcabcc,如果按照原始枚举算法,就要从第一个字符开始比较到最后一个字符,这种时间复杂度就是很麻烦的,但是如果提前记录A的子串,再寻找B与A子串相同的部分,就可以极大的减少时间,设想一下,给你一个abcabcabc……循环的好长好长字符串,再给你一个abcd,这样你就会发现,用枚举的方法,这个超级无比慢,但是你用KMP算,发现abc相同,你只需要移动3个位置,便可以从这个超级超级长的字符串中寻找你需要的东西,然后发现没有输出0,这个例子就很好的说明了KMP的效率极其之高。
4.next数组的求法:
①理论:初始化next[1]=j=0,假定next[1~i -1]已经求出,那么便可以求出next[i]。然后继续扩展next的长度,如果扩展到下个字符不同的时候,令此时的j变为next[j],直到j为0重新匹配。如果可以扩展成功,令j++,next[i]的值就是j。
②代码:
1 int KMP_search(char* s, char* p) 2 { 3 int i = 0; 4 int j = 0; 5 int sLen = strlen(s); //定义长度 6 int pLen = strlen(p); //定义长度 7 while (i < sLen && j < pLen) 8 { 9 10 if (j == -1 || s[i] == p[j]) 11 { 12 i++; 13 j++; 14 } //①如果j = -1,也可以说字串的匹配成功,那么就让i++,j++ 15 else 16 { 17 j = next[j]; 18 } 19 } //②如果j != -1,并且当前字符匹配失败(即S[i] != P[j]),则令 i 不变,j = next[j],就是我前面进行了很简单的解释 20 //next[j]即为j所对应的next值 21 if (j == pLen) 22 return i - j; //这个返回值非常朴素,基本都能看懂我就不解释了 23 else 24 return -1; 25 }5.f函数的求法:
①理论:和next数组的求法是一样的,代码也差不多,所以就不做多的废话了。
6.递归求next数组:
(我认为这个方式学过递归和字符串的人都比较容易理解,理论呢就不多说了,和next求法是一样的,递归的方式可以去看递归,所以我话不多说直接代码)
1 void howtofindnext(char* p, int next[]) 2 { 3 int pLen = strlen(p);//同样是对长度进行定义 4 next[0] = -1; 5 int k = -1; 6 int j = 0; 7 while (j < pLen - 1)//如果j的大小小于了子串的长度,那么执行循环,大了怎么可能是字串嘞 8 { 9 //p[k]表示前缀,p[j]表示后缀,前后缀我前面已经有所解释了 10 if (k == -1 || p[j] == p[k]) 11 { 12 ++j; 13 ++k; 14 //较之前next数组求法,改动在下面4行,这十分重要的!!!!! 15 if (p[j] != p[k]) //前缀与后缀不同 16 next[j] = k; 17 else 18 //因为不能出现p[j] = p[next[j]],所以当出现时需要继续递归,k = next[k] = next[next[k]] 19 next[j] = next[k]; //进行下一次递归 20 } 21 else 22 { 23 k = next[k]; //这个递归方式较上次的next比较容易写,但是我个人认为,上种方法比较容易理解和掌握 24 } 25 } 26 }就是这样结束了,我觉得新手也可以看的懂哦!!!