1,关于模式串目标串的匹配过程,话不多说,先上图
上图中O为目标串,f为模式串,A,B为模式串中最长的可匹配的前缀,后缀。在i之前模式串与目标串是匹配的,i为失配位置。我们知道在模式串中B之前均与A部分不匹配,而这些部分都已与目标串匹配,故A部分不可能与B部分对齐的目标串的前面部分匹配,所以我们可以直接将模式串的A部分移到与目标串中与B部分匹配的部分。(即,目标串可以从匹配失败的地方开始重新匹配,而不必像朴素算法回退到与模式串匹配的第二个位置。模式串也可以根据前后缀的记录从前缀的后一个位置开始匹配)这就是kmp算法高效的原因。
2,有了前面的说明,我们知道记录模式串的模式(即该串中子串的重复情况(个人是这么理解的))是十分重要的。在此,我们引入一个next数组来记录。
show the code:
void getnext()
{
int i,j;
next[0]=0;
for(i=1,j=0;s[i];i++){///j为next[i-1]
while(j>0&&s[i]!=s[j]){///寻找前缀
j=next[j-1];
}
if(s[i]==s[j])++j;///若与前缀匹配,则匹配长度+1
next[i]=j;
}
}
用一个例子来说明一下代码吧:
串: a b c d e f g a b c d a b c e
下标:0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
next: 0 0 0 0 0 0 0 1 2 3 4 1 2 3 0
我们从11号下标对应的位置来看,通过10号下标的next值我们找到了3号下标(在此之前j值为4,通过while循环,j值变为0)(因为next数组记录的是与前缀的匹配长度,而我们的数组下标是从0开始的,所以next数组在j-1位置的值即为串中需要查看是否与j位置的字符匹配的字符的下标),查看0号字符与11号字符是否一致,若一致则后缀长度+1.
kmp算法:
int kmp(char *w,char *t){
int l1,l2,i,j,sum=0;
l1=strlen(w);
l2=strlen(t);
for(i=0,j=0;i<l2;i++){
if(l2-i<l1-j)break;
while(j>0&&w[j]!=t[i]){
j=next[j-1];
}
if(w[j]==t[i])++j;
if(j==l1){
sum++;
j=next[j-1];
continue;
}
}
return sum;
}