KMP算法详解


我会结合代码对KMP算法进行详细讲解。由于编代码和做数据结构的题不一样所以,我分两部分对KMP进行讲解。首先,大家要了解一个字符串的前后缀不能是这个字符串本身。大家先记住这个条件,下面会用到。

手算next串

书上对next串求法的定义是这样的:
n e x t [ j ] = { 0 , j = 1 M a x ( k ∣ 1 &lt; k &lt; j 且 有 &quot; t 1 t 2 . . . t k − 1 &quot; = &quot; t j − k + 1 t j − k + 2 . . . t j − 1 &quot; ) 1 , 其 他 情 况 next[j]=\left\{\begin{matrix} 0,j=1\\ Max(k|1&lt;k&lt;j且有&quot;t_1t_2...t_{k-1}&quot;=&quot;t_{j-k+1}t_{j-k+2}...t_{j-1}&quot;) \\ 1,其他情况 \end{matrix}\right. next[j]=0,j=1Max(k1<k<j"t1t2...tk1"="tjk+1tjk+2...tj1")1,
根据书中定义很难理解next串是如何求解的。
根据一个例子来具体讲解如何手算next串。

j12345678
模式串abaabcac

根据定义很容易知道next[1]=0(注意,这里下表是从1开始)

j12345678
模式串abaabcac
n e x t [ j ] next[j] next[j]0

接下来就需要根据定义中第二个公式依次求解。
首先要明白这个公式表达的是什么意思 M a x ( k ∣ 1 &lt; k &lt; j 且 有 &quot; t 1 t 2 . . . t k − 1 &quot; = &quot; t j − k + 1 t j − k + 2 . . . t j − 1 &quot; ) Max(k|1&lt;k&lt;j且有&quot;t_1t_2...t_{k-1}&quot;=&quot;t_{j-k+1}t_{j-k+2}...t_{j-1}&quot;) Max(k1<k<j"t1t2...tk1"="tjk+1tjk+2...tj1"),其实这个公式求得就是j-1的最长公共前后缀。 &quot; t 1 t 2 . . . t k − 1 &quot; = &quot; t j − k + 1 t j − k + 2 . . . t j − 1 &quot;t_1t_2...t_{k-1}&quot;=&quot;t_{j-k+1}t_{j-k+2}...t_{j-1} "t1t2...tk1"="tjk+1tjk+2...tj1这个式子可以看出是在匹配找最大相同前后缀。最终结果为最长前后缀加一(解释加一的原因,仔细观察Max函数, t 1 t 2 . . . t k − 1 t_1t_2...t_{k-1} t1t2...tk1最终是以k-1结尾,我们取的是k),就拿上面的例子来说,j取5。
因为 t j − k + 1 t j − k + 2 . . . t j − 1 t_{j-k+1}t_{j-k+2}...t_{j-1} tjk+1tjk+2...tj1,所以最多只能匹配到第j-1=4位。

1234
abaa

可以看出他们的最长相同前后缀为a,长度为1
所以j=5时,next[5]=2.
在举个j=6时,

12345
abaab

所以最长相同前后缀为ab,长度为2,则next[6]=3,然后依次求出所有next,得

j12345678
模式串abaabcac
n e x t [ j ] next[j] next[j]01122312

最后还有一个疑问可能是,最后的(next[j]=1,其他情况)是干什么的。因为存在没有任何公共前后缀的情况,所以给这些对应的位置赋值为一,如果对匹配过程比较熟悉的话因该就会知道,这个一其实就是让模式串从头开始匹配。其实最后一个式子基本用不到,因为按第二个公式也能求出一,没有匹配到相同的前后缀,直接取零,然后加一就行了。

编程计算next串

在这里我用c++对算法进行实现。

void getNext(){
   int i=0,j=-1;
   int len=strlen(pattern);
   while(j==-1||i<len){
       if(j==-1||pattern[i]==pattern[j]){
           Next[++i] =++j;
       }else{
           j=Next[j];
       }
   }
}

这里需要注意,计算机数组下标是从零开始的
还是这个例子,这个是上面的算法求出的next数组,和上面的比较一下,其实是让每一位都减了一。

j01234567
模式串abaabcac
n e x t [ j ] next[j] next[j]-10011201

然后详细讲解实现过程,我们将模式串拷贝一份,一份叫做假主串,一串叫做假模式串。这里这样叫是为了让大家不和匹配过程混淆。
我会按照算法匹配的过程实现一下。
next[0]=-1,不用计算,由上面的公式可知,求next[j]的值时, t t t最多只能取到 t j − 1 t_{j-1} tj1,所以求一个串的最长公共前后缀是根据要求的下标之前的串确定的。
i = 0 , j = − 1 , 初 始 位 置 i=0, j=-1,初始位置 i=0,j=1,

01234567
i下标指向i
假主串abaabcac
假模式串abaabcac
-101234567
j下标指向j

执行next[++i]=++j.next[1]=0
i = 1 , j = 0 i=1,j=0 i=1,j=0

01234567
i下标指向i
假主串ab
假模式串ab
-101234567
j下标指向j

pattern.at(i)==pattern.at(j)不相等,执行else语句,j=next[j],即j=next[0]=-1,因为j=-1,所以又执行next[++i]=++j;next[2]=0
i = 2 , j = 0 i=2,j=0 i=2,j=0

01234567
i下标指向i
假主串aba
假模式串aba
-101234567
j下标指向j

执行next[++i]=++j.next[3]=1
i = 3 , j = 1 i=3,j=1 i=3,j=1

01234567
i下标指向i
假主串abaa
假模式串abaa
-101234567
j下标指向j

j = n e x t [ j ] = n e x t [ 1 ] = 0 j=next[j]=next[1]=0 j=next[j]=next[1]=0

01234567
i下标指向i
假主串abaa
假模式串abaa
-101234
j下标指向j

执行 n e x t [ + + i ] = + + j , n e x t [ 4 ] = 1 next[++i]=++j,next[4]=1 next[++i]=++j,next[4]=1
i = 4 , j = 1 i=4,j=1 i=4,j=1

01234567
i下标指向i
假主串abaab
假模式串abaab
j下标指向j

执行 n e x t [ + + i ] = + + j . n e x t [ 5 ] = 2 next[++i]=++j.next[5]=2 next[++i]=++j.next[5]=2
i = 5 , j = 2 i=5,j=2 i=5,j=2

01234567
i下标指向i
假主串abaabc
假模式串abaabc
-101234567
j下标指向j

执行 j = n e x t [ j ] = n e x t [ 2 ] = 0 j=next[j]=next[2]=0 j=next[j]=next[2]=0

01234567
i下标指向i
假主串abaabc
假模式串abaabc
-101234567
j下标指向j

执行 j = n e x t [ j ] = − 1 j=next[j]=-1 j=next[j]=1
执行 n e x t [ + + i ] = + + j , n e x t [ 6 ] = 0 next[++i]=++j,next[6]=0 next[++i]=++j,next[6]=0
i = 6 , j = 0 i=6,j=0 i=6,j=0

01234567
i下标指向i
假主串abaabca
假模式串abaabca
-10123456
j下标指向j

执行 n e x t [ + + i ] = + + j . n e x t [ 7 ] = 1 next[++i]=++j.next[7]=1 next[++i]=++j.next[7]=1.
其实执行到这里已经结束了
i = 7 , j = 1 i=7,j=1 i=7,j=1

01234567
i下标指向i
假主串abaabcac
假模式串abaabcac
-101234567
j下标指向j

执行 j = n e x t [ 1 ] = 0 j=next[1]=0 j=next[1]=0,不匹配,继续执行 j = n e x t [ 0 ] = − 1 j=next[0]=-1 j=next[0]=1
n e x t [ + + i ] = + + j . n e x t [ 8 ] = 1 next[++i]=++j.next[8]=1 next[++i]=++j.next[8]=1
我们显然不需要下标为8的next,但是有一种题是需要这个下标的。
如杭电的这一题:http://acm.hdu.edu.cn/showproblem.php?pid=1686
如果大家仅仅求next串,只需将len-1。(建议不减,不影响最终结果)
根据一步步执行大家可以看出是不是和我们手算最长公共前后缀是不是很像。

最后将完整代码贴出来。这个是上面那道题的题解。

#include<iostream>
#include<cstring>
using namespace std;
int Next[1000010];
char s[1000010];
char pattern[1000010];
void getNext(){
    int i=0,j=-1;
    int len=strlen(pattern);
    while(j==-1||i<len){
        if(j==-1||pattern[i]==pattern[j]){
            Next[++i] =++j;
        }else{
            j=Next[j];
        }
    }
}
int kmp(){
    int i=0,j=0;
    int ans=0;
    int len1=strlen(s);
    int len2=strlen(pattern);
    while(i<len1){
        if(j==-1||s[i]==pattern[j]){
            i++;j++;
        }else{
            j=Next[j];
        }
        if(j==len2){
        	ans++;
		}
    }
    return ans;
    
}
int main(){
    int t;
    cin>>t;
    while(t--){
    	cin>>pattern>>s;
    	Next[0]=-1;
	    getNext();
	    int k=kmp();
	    cout<<k<<endl;
	}
	return 0; 
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值