我们知道,在字符串匹配中,最常用的采用暴力遍历,假设主串为‘s1s2···sn’,模式串为‘p1p2···pm’,我们常从主串的第一个字符开始,匹配模式串的p1、p2直到pm,若中间某个字符匹配失败,我们重新从s2开始匹配模式串,这种算法的时间复杂度达到O(n*m),代码如下
for(int i=1;i<=(n-m);i++){
flag=1;
for(int j=1;j<=m;j++){
if(p[j]!=s[i])
flag=0;
}
if(flag==1)break;
//匹配成功
}
if(flag==0);
//匹配失败
如主串‘0000000000000000000000000000000000000001’,模式串‘000001’,需要在每趟比较时匹配到最后一个字符返回,效率低下,而其实主串和模式串是存在部分匹配的,这时其实我们不需要每次让i++,而是利用得到的部分匹配的结果让模式串尽可能多的向右移动
来举一个例子,主串为‘ababcabcacbab’,模式串为‘abcac’。
若为普通匹配:
1. a b a b c a b c a c b a b
a b c
2. a b a b c a b c a c b a b
a
3. a b a b c a b c a c b a b
a b c a c
4. a b a b c a b c a c b a b
a
5. a b a b c a b c a c b a b
a
6. a b a b c a b c a c b a b
a b c a c
我们发现在进行了第三趟匹配后,实际上第四、五、六趟匹配是没有必要的,因为之前已经扫描了主串的34567即abcab,这时我们只需要找到一个部分匹配,直接最大限度的向右移动模式串即可,而这个寻找部分匹配实际上是可以提前进行的,下面介绍KMP算法。
KMP算法是由D.E.Knuth与V.R.Pratt和J.H.Morris同时发现的,人们称它为克努特-莫里斯-普拉塔操作(简称KMP算法)。此算法可以在O(n+m)时间数量级上完成匹配。
为了实现改进算法,需要解决的问题是当匹配失败时,模式串应该向右滑动多少,也就是说,当主串中第i个字符与模式串中第j个字符匹配失败时,主串中第i个字符应该与模式串中哪一个字符继续比较(而不是模式串中第一个)
我们假设此时应当与模式串中第k(k<j)个字符继续比较,则模式串前k-1个字符与必须满足下列关系,且不存在k`>k满足下列关系
‘p1p2···p(k-1)’=’s(i-k+1)s(i-k+2)···s(i-1)’
而已得到的部分匹配的结果是
‘p(j-k+1)p(j-k+2)···p(j-1)’=’s(i-k+1)s(i-k+2)···s(i-1)’
由上面两式我们可以推出
‘p1p2···p(k-1)’=’p(j-k+1)p(j-k+2)···p(j-1)’
我们令next[j]=k,next[j]表示当前以当前字符结束的字符串中最大共同的前缀和后缀,比如字符串abaabcac,它的next值为00112010(下标从1开始,忽略next[0]),字符串abebabebae的next值为0000120010
下面贴出我改进过得代码
#include<stdio.h>
#include<string.h>
int index_KMP(char *s,char *t,int pos);
void get_next(char *t,int *next);
char s[20]="aaacabaabaabcacaabc";
char t[9]="abaabcac";
int next[9];
int pos=0;
void main(){
int n;
get_next(t,next);
n=index_KMP(s,t,pos);
if(n!=0)
printf("模式串 t 在主串 s 中第 %d 个位置之后。\n",n);
else
printf("主串中不存在与模式串相匹配的子串!\n");
for(int i=1;i<=(int)strlen(t);i++){
printf("%d",next[i]);
}
printf("\n");
}
int index_KMP(char *s,char *t,int pos){
//利用模式串的T的NEXT函数求t在主串s中的第pos个位置之后的位置的KMP算法,(t非空,1<=pos<=Strlength(s)).
int i=pos,j=1;
while (i<=(int)strlen(s)&&j<=(int)strlen(t)){
if (j==0||s[i]==t[j-1]){//继续进行后续字符串的比较
i++;
j++;
}
else
j=next[j-1]+1; //模式串向右移动
}
if (j>(int)strlen(t)) //匹配成功
return i-strlen(t)+1;
else //匹配不成功
return 0;
}
void get_next(char *t,int *next){
//求模式串t的next函数的并存入数组next[]中。
int i=0,j=-1;
next[0]=-1;
while (i<(int)strlen(t)){
if (j==-1||t[i]==t[j])
next[++i]=++j;
else
j=next[j];//在这里我们要将求模式串的最大前缀后缀同样看成一个KMP问题
}
}
在学习网上的KMP算法时,发现有一些代码直接复制运行会存在错误,某些字符串不能查找成功,一些数组的next值求的也和我理解的不同,所以特地重新写了代码,终于搞懂了KMP算法。