字符串匹配-KMP算法

Knuth-Morris-Pratt算法(简称KMP算法)以三个发明者命名,K就是著名的科学家Donald Knuth。这个算法不好理解,我试着参考网上的资料讲清楚。考虑下面这张图片描述的情况。

字符串匹配-KMP算法_第1张图片

在字符串BBC ABCDAB ABCDABCDABDE中匹配ABCDABD,当匹配到如图所示的位置时,前六个字符ABCDAB都匹配成功了,空格和D没有匹配上。如果按照朴素的匹配算法,就会将ABCDABD向后移动一位开始新的匹配。其实这个时候我们已经知道了前六个字符匹配成功的事实,但是匹配的朴素算法并没有对此加以利用,所以效率很低。如果我们能提前计算一些信息,就可以多向前移动几位。下图描述了在字符串O中寻找f,当匹配到位置i时两个字符串不相等,向前移动了k位的情况。

字符串匹配-KMP算法_第2张图片

观察这幅图片我们发现,在向前移动了k位之后A和B必然要是相等的。令f在位置i之前(不包括位置i)的子串为f1,很显然,A是f1的前缀,B是f1的后缀。所以kmp算法的核心即是计算字符串f每一个位置之前的子串的前缀和后缀的最大公共长度(不包括字符串本身)。我们用next[i]表示前i个字符的最大公共长度并且规定next[1]=next[0]=0。比如说,对于字符串cacca,next[2]=0,next[3]=1(c),next[4]=1(c),next[5]=2(ca)。获得f每一个位置的最大公共长度之后,就可以利用该最大公共长度快速和字符串O比较。当每次比较到两个字符串的字符不同时,我们就可以根据最大公共长度将字符串f向前移动(已匹配长度-最大公共长度)位,接着继续比较下一个位置。事实上,字符串f的前移只是概念上的前移,只要我们在比较的时候从最大公共长度之后比较f和O即可达到字符串f前移的目的。对应于上图所示的例子,我们应该向后移动四位。

字符串匹配-KMP算法_第3张图片

现在的问题是如何计算next[i]。如果仔细观察,可以发现构造next[i]的时候,可以利用next[i-1]的结果。假设已求得next[10]=3。

000#xxx000

字符串的前十个字符

000

末尾3个字符

这意味着末尾3个字符和前3个字符是一样的。为求next[11],可以直接比较第4个字符和第11个字符。

000#xxx000#

字符串的前十一个字符     

000#            

末尾4个字符

如果它们相等,next[11]=next[10]+1=4,这是因为next[10]=3保证了前3个字符和末尾4个字符的前3个字符是一样的。如果它们不相等,我们可以将长度为next[i]的字符串继续分割,获得其最大公共长度next[next[i]],然后再和位置i的字符比较。这是因为长度为next[i]前缀和后缀都可以分割成上部的构造,如果位置next[next[i]]和位置i的字符相同,则next[i+1]就等于next[next[i]]加1。如果不相等,就可以继续分割长度为next[next[i]]的字符串,直到字符串长度为0为止。

字符串匹配-KMP算法_第4张图片

以hihoCoder1015的AC代码为例给出KMP算法的示例代码:

#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
int	Next[10010];
char Pattern[10010],Text[1000010];
int m,n;

void compute_next(int *Next,char *Pattern)  
{  
    int k=0;  
    Next[0]=Next[1]=0;  
	//Next[i]表示Pattern[0]到[i-1]的最大公共长度,k是Next[i-1]的值 
    for(int i=2;i<=m;i++) 
	{  
 		for(;k!=0&&Pattern[k]!=Pattern[i-1];k=Next[k]);  
        if(Pattern[k]==Pattern[i-1]) k++;  
        Next[i]=k;  
    }  
}

int kmp_match(char *Text,char *Pattern,int *Next)  
{  
    int i=0,j=0,ans=0;  
    //i表示上一次迭代匹配了多少个字符 
    //j表示这次迭代从Text的哪个字符开始比较
	//ans表示匹配成功的次数   
    while(j<n) 
	{  
        for(i=Next[i];i<m&&Pattern[i]==Text[j];i++,j++);  
        //直接从Next[q]的位置开始匹配 
        if(i==0) j++;  
        //如果一个也没有匹配j向后移动一位 
        else if(i==m) ans++; 
		//如果匹配成功ans++ 
    }  
    return ans;
}

int main()  
{  
	int N;
	scanf("%d",&N);
	while(N--)
	{
		scanf("%s %s",Pattern,Text);
   		m=strlen(Pattern),n=strlen(Text); 
    	compute_next(Next,Pattern);  
    	cout<<kmp_match(Text,Pattern,Next)<<endl;  
	}
}
参考资料:

KMP算法详解

kmp算法的理解与实现

字符串匹配的KMP算法







你可能感兴趣的:(字符串匹配-KMP算法)