首页 > 代码库 > 我所理解的KMP
我所理解的KMP
KMP算法是一种用于字符串匹配的算法,由D.E.Knuth与V.R.Pratt和J.H.Morris同时发现,所以叫KMP算法。
字符串匹配,就是有一个目标字符串S和模式字符串P,然后查找P在S中是否有出现,出现的话,位置是什么地方。
最简单粗暴的方法就是逐个字符比较,从S的第0个字符开始,和P的第0个字符比较,如果相等,再比较后面一个,如果在第n个出现不想等,那么就把S置回第1个(上一次的后面一个),P置回第0个字符,然后再开始一轮比较,很明显,这样比较的效率非常低下,如果S串的长度为m,P串的长度为n,时间复杂度为O(mn)。
KMP的思想是先对模式串P做一些预处理,然后在逐个比较的时候可以跳过一些明显不等的比较(比如a=b,a<>c,那么b必然是<>c的,不需要再比较)。
首先,对模式串P求出next函数,结果为一个数组,next[j]的值表示P[0...j-1]中最长后缀的长度等于相同字符序列的前缀,求解next函数的规则:
next[j] = -1 j = 0
next[j] = max(k): 0<k<j P[0...k-1]=P[j-k,j-1]
next[j] = 0 other
还是一样的逐个字符比较,然后在发生不匹配的时候,并不是从头再开始,对于next[j]>0的情况,可以直接跳转到next[j]的位置再进行比较,跳过前面next[j]个字符的比较,因为肯定是可以匹配的。
那么问题来了:为什么直接跳转到next[j]的位置再进行比较,而前面跳过的前面next[j]个字符肯定是匹配的呢?
原因就在next函数的定义,再来看一遍:next[j]的值表示P[0...j-1]中最长后缀的长度等于相同字符序列的前缀。根据这个定义,就是说如果next[j]=k>0,那么P[0...k-1]=P[j-k,j-1],由于S[s...s+j-1]和P[0...j-1]是相等的,根据相等的传递性,S中目前匹配到的最后next[j]个字符肯定和P中的前next[j]个字符是匹配的。
看个直观的例子:
S:x y x y u y x z
P:x y x y x
还是一个一个字符比较,前面第0,1,2,3个都是相等的,到第4个的时候,出现不想等,那么,可以之后去比较P的next[4] = 2的位置,P[0]=S[2],P[1]=S[3],是不用比较的,因为next[4] = 2,就表示P[0]=P[2],P[1]=P[3],而比较到第4个出现不等,证明前0,1,2,3个都是相等的,即P[2]=S[2],P[3]=S[3],这样肯定可以推导出P[0]=S[2],P[1]=S[3]。
下面是KMP算法的一种C的代码实现:
int KMPMAtch(char *s, chap *p) { int i, j, next[1000]; i = j = 0; initNextValues(p, next); while(i<strlen(s)){ if(j=-1 || s[i]==p[j]){ i++; j++; } else { j=next[j]; } if(j==<strlen(p)){ return i-j; } } } void initNextValues(char *p, int *next) { int j,k; next[0]=-1; j=0; k=-1; while(j<strlen(p)-1) { if(k==-1||p[j]==p[k]) { j++; k++; next[j]=k; } else k=next[k]; } }
注:本文参考了很多网上介绍KMP算法的资料和博客,实在是太多了,不一一写明出处了。
我所理解的KMP