KMP算法:学习笔记

来源:互联网 发布:管家婆单机版软件 编辑:程序博客网 时间:2024/06/05 05:37

—一种字符串的快速匹配算法:
举个栗子:
找S2在S1中出现的位置:

正常的做法:先从第一个位置开始匹配,匹配到S2的最后一位时,发现不同,这时,把S2右移一位,再从头开始匹配,如图:

如此匹配下去,直到:

但是,我们可以看出,前面的三次匹配都是多余的,我们可不可以直接跳到这一步呢?
——KMP算法
我们一开始是匹配到最后一位d和a不同,也就是说,这之前的6位都是匹配的,在这前6个字符里,我们发现前缀ab和后缀ab是相同的,也就是S2前两位和S1的前六位的后两位也是相同的,那么我们可以直接跳到这里开始往下比较,S2的第三位和S1的第七位,但是,我们怎么知道下一步应该跳到哪个位置呢?
其实不难看出,我们往后跳的步数就是6-2=4,也就是已经匹配的串的长度减去最长的前缀和后缀相等的长度
所以,我们可以预先处理S2以每一位字符结尾的串的最长的前缀和后缀相等的长度(这两个前缀后缀我们在后面简称最长前缀和最长后缀),就是KMP算法中的next数组
※※如何计算S2的next数组?
对于本例中的S2,我们不难看出:每一位对应的next值为:
abcdabd
0000120
或者我们举一个更好理解的例子:
ababa,它的next数组为:00123
为了容易区分我们把它复制成两个串,比较一个串的前缀和另一个串的后缀:
ababa
ababa
假设我们已经求出了next[4]=2,如何通过next[4]求next[5]?

设p=next[4]=2;
现在我想通过next[4]拓展next[5],那么比较S[p+1]和S[5],发现相同,都是a,那么next[5]=p+1;
如果不同呢?
再举一个例子:
这里写图片描述
在求next[10]时,发现S[5]和S[10]不相同,这时p=next[9]=4,如果相同的话,这时next[10]=p+1=5,现在不同了,next[10]的值肯定要小了,那我们往前找。
怎么找呢?
对于next[9]的最长前缀后缀abab(红色)这个串,我们可以知道他的最长后缀和整个串的与他长度相同的前缀是一样的,也就是S[1~2]=S[8~9],如果S[3]==S[10],那么next[10]就等于2+1=3,也就是next[p]+1,在这个例子中是成立的,所以我们的next[10]就=3了。当然,显而易见的是,这个值不会再大了
如果在对next[p]的匹配中依然没有匹配成功,那按照同样的方法,让p=next[p],继续找下去,直到找到next[p+1]==next[10]或者p=0也就是找到头了,停止循环
代码体会:

void make_next(){    int m=strlen(s2);    next[1]=0;    for(int i=2;i<=m;++i)    {        int p=next[i-1];        while(p&&s2[i]!=s2[p+1])          p=next[p];        if(s2[i]==s2[p+1])           p++;        next[i]=p;    }}

有了next数组,我们接下来的匹配就很简单了:
其实和求next数组的思想有一定的相似之处,如果理解了上面的代码,求kmp的代码就不难了:

void kmp(){    int n=strlen(s1);    int m=strlen(s2);    int p=0;    for(int i=1;i<=n;++i)    {        while(p&&s2[p+1]!=s1[i])          p=next[p];        if(s1[i]==s2[p+1])           p++;        if(p==m)        {            printf("%d ",i-p+1);            p=next[p];        }    }}