KMP算法的next数组的理解

对于这一个问题:问字符串S中有多少子串与字符串M相同;即

S :  abbaeababbae

M: bbae

可以看出来 S中有两个子串与M相同

我们可以采用最暴力的算法进行计算:

如图:

abbaeababbae
bF
bbaeT
bbF
bF

                       ........

    

 我们将M的每一位与S对应的位置进行比较,比较完后判断是否相等,然后进行"移动";

这样就可以统计完S里有多少个M

 代码如下:

//S=abbaeababbae
//M=bbae
std::string s="abbaeababbae"
std::string m="bbae"

int cnt=0;

for(int i=0;i<s.size()-m+1.size();i++){
     bool flag=true;
    for(int j=0;j<m.size();j++){
         if(s[i+j]!=m[j]){
         flag=false;
         break;
         }
     }  
   if(flag==true)cnt++;
}

可以看到:我们需要移动 n-m次   比较 (1 ~ m )次 最差情况的时间复杂度就是O(n*m)

接下来介绍的kmp的思想由于文章不能很生动的表示 

推荐看这个up的视频:「天勤公开课」KMP算法易懂版_哔哩哔哩_bilibili

了解完kmp后接下来介绍next数组的实现;

我们记S为主串,M为模式串;设字符数组的索引从1开始;   使M=" "+M;

next 数组就是一个预处理

定义:1:next[j]值  为 下一次比较时在模式串的第 next[j] 位 进行比较(怎么用next)

          2: next[j]=  j之前的最长公共前后缀长度+1;(j之前不包含j)规定next[1]=0;

  我们来看看next数组怎么用的,再来看怎么不直接用定义实现next数组;

还是看前面的图:

先根据定义 手算一下next数组:next[j]=  j之前的最长公共前后缀长度+1;

next[1]=0; (规定)

不可以认为 2前面的 b既可以看做前缀和后缀,因为没意义; 

得到:next[2]=0+1;

       

 得到next[3]=1+1

b!=a   bb!=ba;  

得到next[4]=0+1;

我们还要求一下next[5]: 同样可以得到next[5]=0+1;

所以M的next数组为:next[1]=0,next[2]=1,next[3]=2,next[4]=1,next[5]=1;

假设我们通过代码已经求出了next数组:

再来求S:

主要的思路是j指向的i的位置  s[i]!=M[j]就调用next数组

 

 模式串的第一位与主串不同  next[1]=0  M直接往后移一位

cnt++; 

比较相同的话j往后移一位;j移到了M的后面 即j=5;

next[j]==next[5]==1;

模式串移到的位置使得:    M[ next[j] ]为j指的位置(这个和next[1]并不一样)

---->

 再进行比较:

 next[1]:直接后移

j==2  : next[2]=1;-->

 j==1 next[1] :-->

 

cnt++;

这是时候j 指向的i到了主串的后面比较结束;

 知道了怎么用next数组后现在来看怎么得到next数组:

如果我们按照定义求完最长公共前后缀 ,加个一得到next[j]的话,效率就不太高了

接下来介绍一个高效的方式,通过递推得到next数组 next[1]=0;

因为next数组只与模式串有关所以下面只例举模式串

来看这个模式串

 这里要递推得到next[j+1]的话就要知道next[1],next[2].....next[j];

这个模式串的next[]={x,0,1,1,1,2,1,1,2,3,4,5,6,7,8};next[0]可以不管;

最后一项next[14]=8;再看下图:

 我们如何根据字符 'x' 以及 j+1前面的 next 求出来next[j+1]呢?

对了next有一个性质:next[j+1]<=next[j]+1;(因为就往后推了一位,所以无论如何next的每一项都不会比前一项大于2)

通常采用假设法:

   此时j==14  

if  X=="B"( M(8) ) :

      next[15]=8+1

else: 

    令 j=8;   next[8]==2

      

       如图: 因为:由next[15]知道:M(1~7)==M(7~13)  ;又由next[8]知道: M(1)==M(7); (后面的同理)

                           得到M(1)==M(7)==M13)

                           因此有:

                     if   X=="B"( M(2) ):

                           next[15]=2+1  看图解释:

                      else:

                            令:j=2

                                   if:   X=="A"(M(1)):

                                          next[15]=1+1

                                      else: 

                                          next[15]=0+1  (因为第一个和他不同那么前后缀为0)

就这样无论x等于什么next[15]总能通过找到一个答案;

那么这个代码是怎么写的呢?总不能套一堆if else 吧:

我们从上面的 的带颜色的句子截取下来:

你会发现现那个 令j=...似乎毫无逻辑却又非常有规律

                              这便是这个next数组求解的神奇之处

                               若想知道为什么可自行百度它的数学推理

 要求next[15]

                 j==14:   next[14]=8;

              if x==M[8] 

                  next[15]=next[14]+1

              else

                 j=8:       next[8]=2

                      if x==M[2]

                          next[15]=next[8]+1

                        else

                           j=2  next[2]=1

                            if x==M[1]

                                 next[15]=next[2]+1;

                             else  

                                  next[15]=1   ||  next[15]=next[1]+1

这看起来像动态规划还是像递归? 

 接下来的代码是从next[1]开始递推推到next[M.size()]

看代码:

//M为模式串:
void getnext(string& M,int*next){

  int i=1,j=0;next[1]=0;

  while(i<M.size()){
    if(j==0){
     next[i+1]=1;
     j=1;
     i++;
    }
    else if(M[i]==M[j]){
     next[i+1]=j+1;
     i++;
     j++;
    }
    else{
     j=next[j];
    }
  }

}
//--------------------------------------------
//极简代码:
void getnext(string& M,int*next){
 int i=1,j=0;next[1]=0;
 while(i<M.size()){
   if(j==0||M[i]==[j])next[++i]=++j;
   else j=next[j];
 }
}

时间复杂度:next 玄学;最差是线性;

                   与主串比较(N+M)

求出这个next就可以一劳永逸了~

最后再附上一句经典:

一个人能走的多远不在于他在顺境时能走的多快,而在于他在逆境时多久能找到曾经的自己。
————KMP

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值