擴展KMP


劉雅瓊PPT講解鏈接: http://wenku.baidu.com/view/8e9ebefb0242a8956bece4b3.html

擴展KMP:
    給出模板串A和子串B,長度分別為lenA和lenB,要求在線性時間內,對於每個A[i](0<=i<lenA),
 
    求出A[i..lenA-1]與B的最長公共前綴長度,記為ex[i](或者說,ex[i]為滿足A[i..i+z-1]==B[0..z-1]的最大的z值)。
 
    擴展KMP可以用來解決很多字符串問題,如求一個字符串的最長回文子串和最長重復子串。

【算法】
    
    設next[i]為滿足B[i..i+z-1]==B[0..z-1]的最大的z值(也就是B的自身匹配)。設目前next[0..lenB-1]與ex[0..i-1]均已求出,要用它們來求ex[i]的值。

    設p為目前A串中匹配到的最遠位置,k為讓其匹配到最遠位置的值(或者說,k是在0<=i0<i的所有i0值中,使i0+ex[i0]-1的值最大的一個,p為這個最大值,即k+ex[k]-1),
    
    顯然,p之后的所有位都是未知的,也就是目前還無法知道A[p+1..lenA-1]中的任何一位和B的任何一位是否相等。
    
    根據ex的定義可得,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],這就說明ex[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或更大),這樣, 可以直接得到ex[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值就是ex[i]的值)。在這種情況下,p的值必然會得到延伸,因此更新k和p的值。
    
    邊界:ex[0]的值需要預先求出,然后將初始的k設為0,p設為ex[0]-1。
 
對於求next數組,也是“自身匹配”,類似KMP的方法處理即可。唯一的不同點也在邊界上:可以直接知道next[0]=lenB,next[1]的值預先求出,然后初始k=1,p=ex[1]。

需要嚴重注意的是,在上述的情況(2)中,本該從A[p+1]與B[p-i+1]開始匹配,但是,若p+1<i,也就是p-i+1<0(這種情況是有可能發生的,當ex[i-1]=0,且前面的ex值都沒有延伸到i及以后的時候)的話,需要將A、B的下標都加1(因為此時p必然等於i-2,如果A、B的下標用兩個變量x、y控制的話,x和y都要加1)!!

【時間復雜度分析】

    在KMP和擴展KMP中,不管是A串還是B串,其匹配位置都是單調遞增的,故總時間復雜度是線性的,都為O(lenA + lenB)(只是擴展KMP比KMP的常數更大一些)。

【應用】

    KMP和擴展KMP在解決字符串問題中有大用。很多看上去很猥瑣的字符串問題,都可以歸結到這兩種算法之中。另外,這里的“字符串”可以延伸為一切類型的數組,而不僅僅是字符數組。
 
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
*/ 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM