对KMP算法的认识和总结
I.什么是KMP算法
KMP算法适用于模式串对文本的快速匹配,在暴力的匹配算法中,需要遍历文本串,设文本串的长度为n,模式串的长度为m,时间复杂度为O(n*m),在大数据下显然不行的。KMP算法则可以在最坏情况O(n+m)下完成匹配。
II.KMP算法中的匹配过程
next[]数组是KMP算法的精髓,首先给出例子:ababacab,字符串的下标从0开始。此时的next[]为{-1,-1,0,1,2,-1,0,1};(具体怎么来的可以先无视)即,next[i]=k,表示的是,如果第i+1个字符无法匹配,则模式串应回到k位置上进行匹配,即拿模式中的第k+1个字符和文本串中当前要匹配的字符进行匹配。
比如:
i:0 1 2 3 4 5
ab a b a a b a b a c a b --- String s
k:a b a b a c a b --- String p
01 2 3 4
当前匹配到k=4,i=5(表示前面的都匹配成功了),此时p[k+1]!= s[i],即c!=a,按朴素的匹配算法的话,模式串p应该向右移动一格,即从p[0]和s[1]开始匹配起。而KMP算法则根据next[]对应的值去尽可能往右移动更多一点----因为根据next[]可以知道,之前的一些匹配是必然不成功的。那么刚才的例子的话,应该是这样移动的:
i: 0 1 2 3 4 5
ab a b a a b a b a c a b --- String s
k:- - a b a b a c a b --- String p
01 2
即拿p[3]与s[5]匹配。注意到next[4]=2,说明了,若4+1=5无法匹配,那么模式串p应该移动到自己的位置2中,即拿2+1=3与s[5]匹配。例子中p[3]仍然不等于s[5],又next[2]=0,故下一步p回到了自己0的位置,即拿0+1=1与s[5]匹配。如下所示:
i: 0 1 2 3 4 5
ab a b a a b a b a c a b --- String s
k: - - - - a b a b a c a b --- String p
0
此时p[1]仍然不等于s[5],这时next[0]=-1,已经无法移动了,那么将p整体向右移动一格。
i: 0 1 2 3 4 5
k: a b a b a a b a b a c a b --- String s
- - - - - a b a b a c a b --- String p
0
接下来,便匹配成功了。
III.next[]数组的求法以及匹配过程的代码实现
可以知道,next[0]= -1,因为0前面没有字符了。再考虑p[1],如果p[1]= p[0],显然next[1]= 0,因为当p[2]无法匹配时,说明p[0]和p[1]是已经完成匹配的了,即p[0]= s[i],p[1]= s[i+1],而p[0]= p[1],显然可以拿p[0]去匹配s[i+1]。所以next[1]= 0,表示1+1=2匹配失败时,p串回到0,拿0+1=1与s[i+2]匹配。相反,若p[1]!= p[0],则next[1]= -1。
所以,next[]数组的求法如下:
void getNext() { next[0] = -1; int k = -1; for(int i=1; i<n; i++) { while(k>-1 && p[k+1]!=p[i]) k = next[k]; if(p[k+1] == p[i]) ++k; next[i] = k; } }
匹配过程的代码实现:
int KMP() { int lens = (int)strlen(s); int lenp = (int)strlen(p); int k = -1,ret = 0; for(int i=0; i<lent; i++) { while(k > -1 && p[k+1] != s[i]) k = next[k]; if(p[k+1] == s[i]) ++k; /* ----------- 1 if(k == lenp) { ret ++; k = next[k]; } */ /* ----------- 2 if(k == lenp) return true; */ } //return ret; --- 1 //return false; --- 2 }
其中1代表的是求能匹配多少次,2代表能否匹配,根据题目要求而定。While循环表示的就是II中匹配的移动过程。
IV.next[]数组的进一步应用:循环节
比如,ababab是由三个ab组成的,ab就是循环节。
根据next[]的性质,设模式串为p,长度为len,则当(len%(len-next[len-1]-1)==0时,len-next[len-1]-1就是循环节的长度。举个例子,ababab,nex[]为{-1,-1,0,1,2,3},len=6,next[len-1]=3,len-next[len-1]-1= 2。为什么呢?考虑next[n]= k,即如下所示:
s[0]s[1] s[2] ….. s[k] …...
x
…...s[m] s[m+1] …. …. s[n]
设某个位置x,使得n-k== k-x,由于s(0~k)==s(m~n)且n-k==k-x,所以s(0~k-x-1)==s(m,n-k-1),即
s[0]s[1] s[2] …. s[k-x-1]....
…......s[m]s[m+1] …. s[n-k-1]
得到的子问题是一样的。所以可以得到循环节就是len-next[len-1]-1,因为模式串的下标是从0开始的,故有-1。
还可以根据next[]求出模式串中,既是前缀又是后缀的子串。其实就是从串的末尾len-1开始算起,不断求出next[],next[]>-1的都是答案(需要+1,即next[]+1)。为什么呢?比如ababab,next[]为{-1,-1,0,1,2,3},3+1=4是答案,因为next[5]之所以为3,是因为p[2],p[3],p[4]都在前缀中出现过,根据getNext()不难知道这个结论,因为只有p[i]=p[k+1]时,k才进行自增。