首页 > 代码库 > 我所理解的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