拓展kmp

扩展KMP:

  给出模板串A和子串B,长度分别为lenA和lenB,要求在线性时间内,对于每个A[i](0<=i<lenA),求出A[i..lenA-1]与B的最长公共前缀长度,记为extend[i](或者说,extend[i]满足A[i..i+z-1]==B[0..z-1]的最大的z值)。

 

 

  设next[i]为满足B[i..i+z-1]==B[0..z-1]的最大的z值(也就是B的自身匹配)。设目前next[0..lenB-1]与extend[0..i-1]均已求出,要用它们来求extend[i]的值。

 

       设p为目前A串中匹配到的最远位置,k为让其匹配到最远位置的值(或者说,k是在0<=i0<i的所有i0值中,使i0+extend[i0]-1的值最大的一个,p为这个最大值,即k+extend[k]-1)

 

 

   显然,p之后的所有位都是未知的,也就是目前还无法知道A[p+1..lenA-1]中的任何一位和B的任何一位是否相等。
    根据extend的定义可得,A[k..p]==B[0..p-k],因为i>k,所以又有A[i..p]==B[i-k..p-k],设L=next[i-k],则根据next的定义有B[0..L-1]==B[i-k..i-k+L-1]。考虑i-k+L-1与p-k的关系:

    

  1)i-k+L-1<p-k,即i+L<=p。这时,由A[i..p]==B[i-k..p-k]可以得到A[i..i+L-1]==B[i-k..i-k+L-1],又因为B[0..L-1]==B[i-k..i-k+L-1]所以A[i..i+L-1]==B[0..L-1],这就说明extend[i]>=L。又由于next的定义可得,A[i+L]必然不等于B[L](否则A[i..i+L]==B[0..L],因为i+L<=p,所以A[i..i+L]==B[i-k..i-k+L],这样B[0..L]==B[i-k..i-k+L],故next[i-k]的值应为L+1或更大),这样,可以直接得到extend[i]=L!


    

    (2)i+k-L+1>=p-k,即i+L>p。这时,首先可以知道A[i..p]和B[0..p-i]是相等的(因为A[i..p]==B[i-k..p-k],而i+k-L+1>=p-k,由B[0..L-1]==B[i-k..i-k+L-1]可得B[0..p-i]==B[i-k..p-k],即A[i..p]==B[0..p-i]),然后,对于A[p+1]和B[p-i+1]是否相等,目前是不知道的(因为前面已经说过,p是目前A串中匹配到的最远位置,在p之后无法知道任何一位的匹配信息),因此,要从A[p+1]与B[p-i+1]开始往后继续匹配(设j为目前B的匹配位置的下标,一开始j=p-i+1,每次比较A[i+j]与B[j]是否相等,直到不相等或者越界为止,此时的j值就是extend[i]的值)。在这种情况下,p的值必然会得到延伸,因此更新k和p的值。

    

边界:ex[0]的值需要预先求出,然后将初始的k设为0,p设为ex[0]-1。

 

 

对于求next数组,也是“自身匹配”,类似KMP的方法处理即可。唯一的不同点也在边界上:可以直接知道next[0]=lenB,next[1]的值预先求出,然后初始k=1,p=extend[1]。

需要严重注意的是,在上述的情况(2)中,本该从A[p+1]与B[p-i+1]开始匹配,但是,若p+1<i,也就是p-i+1<0(这种情况是有可能发生的,当extend[i-1]=0,且前面的extend值都没有延伸到i及以后的时候)的话,需要将A、B的下标都加1(因为此时p必然等于i-2,如果A、B的下标用两个变量x、y控制的话,x和y都要加1)!!
 

时间复杂度:

  在KMP和扩展KMP中,不管是A串还是B串,其匹配位置都是单调递增的,故总时间复杂度是线性的,都为O(lenA + lenB)(只是扩展KMP比KMP的常数更大一些)。

应用:

        求解最长公共前缀长度,求解字符串中重复子串的长度

练习题:poj2185

C/C++ 模板
#include<iostream>
using namespace std;
const int N = 101010;
int next[N],extand[N];
void getnext(char *T){// next[i]: 以第i位置开始的子串 与 T的公共前缀 
     int i,length = strlen(T);
     next[0] = length;
     for(i = 0;i<length-1 && T[i]==T[i+1]; i++);
          next[1] = i;
          int a = 1;
          for(int k = 2; k < length; k++){
                  int p = a+next[a]-1, L = next[k-a];
                  if( (k-1)+L >= p ){
                       int j = (p-k+1)>0? (p-k+1) : 0; 
                       while(k+j<length && T[k+j]==T[j]) j++;// 枚举(p+1,length) 与(p-k+1,length) 区间比较 
                       next[k] = j, a = k;
                  } 
                  else next[k] = L;
         } 
}
void getextand(char *S,char *T){
   memset(next,0,sizeof(next)); 
         getnext(T); 
         int Slen = strlen(S), Tlen = strlen(T), a = 0;
         int MinLen = Slen>Tlen?Tlen:Slen;
         while(a<MinLen && S[a]==T[a]) a++;
         extand[0] = a, a = 0;
         for(int k = 1; k < Slen; k++){
              int p = a+extand[a]-1, L = next[k-a];  
              if( (k-1)+L >= p ){
                   int j = (p-k+1)>0? (p-k+1) : 0; 
                   while(k+j<Slen && j<Tlen && S[k+j]==T[j] ) j++; 
                   extand[k] = j;a = k; 
              } 
              else extand[k] = L;         
         }
}

int main(){
             char s[N],t[N];
             while(~scanf("%s %s",s,t)){
                      getextand(s,t); 
                      for(int i = 0; i < strlen(t); i++)
                               printf("%d ",next[i]);
                      puts(""); 
                      for(int i = 0; i < strlen(s); i++)
                               printf("%d ",extand[i]);
                      puts("");
             } 
}
/*
aaaabaaa aaaa
*/

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值