1. 程式人生 > >擴展KMP算法小記

擴展KMP算法小記

問題: spa 過程 完成 div 讀者 位置 兩種 直接

參考來自《拓展kmp算法總結》:http://blog.csdn.net/dyx404514/article/details/41831947

擴展KMP解決的問題:

定義母串S和子串T,S的長度為n,T的長度為m;

求 字符串T 與 字符串S的每一個後綴 的最長公共前綴;

也就是說,設有extend數組:extend[i]表示T與S[i,n-1]的最長公共前綴,要求出所有extend[i](0<=i<n)。

(註意到,如果存在若幹個extend[i]=m,則表示T在S中完全出現,且是在位置i出現,這就是標準的KMP問題,所以一般將它稱為擴展KMP算法。)

下面舉一個例子,S=”aaaabaa”,T=”aaaaa”;

首先,計算extend[0]時,需要進行5次匹配,直到發生失配:技術分享圖片從而得知extend[0]=4;

下面計算extend[1],在計算extend[1]時,是否還需要像計算extend[0]時從頭開始匹配呢?

答案是否定的,因為通過計算extend[0]=4,從而可以得出S[0,3]=T[0,3],進一步可以得到 S[1,3]=T[1,3];

計算extend[1]時,事實上是從S[1]開始匹配;

設有輔助數組next[i]表示T[i,m-1]和T的最長公共前綴長度,

在這個例子中,next[1]=4,即T[0,3] = T[1,4],進一步得到T[1,3]=T[0,2],所以S[1,3]=T[0,2],所以在計算extend[1]時,通過extend[0]的計算,已經知道S[1,3]=T[0,2];

所以前面3個字符已經不需要匹配,直接匹配S[4]和T[3]即可,這時一次就發生失配,所以extend[1]=3.

1. 拓展kmp算法一般步驟

通過上面的例子,事實上已經體現了拓展kmp算法的思想,下面來描述拓展kmp算法的一般步驟。

首先我們從左到右依次計算extend數組,在某一時刻,設extend[0...k]已經計算完畢,並且之前匹配過程中所達到的最遠位置為P,所謂最遠位置,嚴格來說就是i+extend[i]-1的最大值(0<=i<=k);

設取到這個最大值的位置為Po,如在上面的例子中,計算extend[1]時,P=3,Po=0.

技術分享圖片

現在要計算extend[k+1],根據extend數組的定義,可以推斷出S[Po,P]=T[0,P-Po],從而得到 S[k+1,P]=T[k-Po+1,P-Po],令len=next[k-Po+1](next[i]表示T[i,m-1]和T的最長公共前綴長度);

分以下兩種情況討論:

第一種情況:k+len<P

如下圖所示:

技術分享圖片

上圖中,S[k+1,k+len]=T[0,len-1],

然後S[k+len+1]一定不等於T[len],因為如果它們相等,則有S[k+1,k+len+1]=T[k+po+1,k+Po+len+1]=T[0,len],那麽next[k+Po+1]=len+1,這和next數組的定義不符;

所以在這種情況下,不用進行任何匹配,就知道extend[k+1] = len.

一個例子的模擬:

  技術分享圖片

第二種情況: k+len>=P

如下圖:

技術分享圖片

上圖中,S[P+1]之後的字符都是未知的,也就是還未進行過匹配的字符串,所以在這種情況下,就要從S[P+1]和T[P-k+1]開始一一匹配,直到發生失配為止,當匹配完成後,如果得到的extend[k+1]+(k+1)大於P則要更新未知P和Po.

另一個例子的模擬:

技術分享圖片

至此,拓展kmp算法的過程已經描述完成,細心地讀者可能會發現,next數組是如何計算還沒有進行說明;

事實上,計算next數組的過程和計算extend[i]的過程完全一樣,將它看成是以T為母串,T為子串的特殊的拓展kmp算法匹配就可以了;

計算過程中的所需要的next數組值全是已經計算過的,所以按照上述介紹的算法計算next數組不會出現問題,不再贅述。

代碼模板:

const int MAX=100010; //字符串長度最大值
int Next[MAX],extend[MAX];

//預處理計算Next數組
void getNext(char str[])
{
    int i=0,j,po,len=strlen(str);
    next[0]=len; //初始化next[0]
    while(str[i]==str[i+1] && i+1<len) i++; next[1]=i; //計算next[1]
    po=1; //初始化po的位置
    for(i=2;i<len;i++)
    {
        if(next[i-po]+i < next[po]+po) //第一種情況,可以直接得到next[i]的值
            next[i]=next[i-po];
        else //第二種情況,要繼續匹配才能得到next[i]的值
        {
            j = next[po]+po-i;
            if(j<0) j=0; //如果i>po+next[po],則要從頭開始匹配
            while(i+j<len && str[j]==str[j+i]) j++; next[i]=j;
            po=i; //更新po的位置
        }
    }
}

//計算extend數組
void EXKMP(char s1[],char s2[])
{
    int i=0,j,po,len=strlen(s1),l2=strlen(s2);
    getNext(s2); //計算子串的next數組
    while(s1[i]==s2[i] && i<l2 && i<len) i++; extend[0]=i;
    po=0; //初始化po的位置
    for(i=1;i<len;i++)
    {
        if(next[i-po]+i < extend[po]+po) //第一種情況,直接可以得到extend[i]的值
            ex[i]=next[i-po];
        else //第二種情況,要繼續匹配才能得到extend[i]的值
        {
            j = extend[po]+po-i;
            if(j<0) j=0; //如果i>extend[po]+po則要從頭開始匹配
            while(i+j<len && j<l2 && s1[j+i]==s2[j]) j++; extend[i]=j;
            po=i; //更新po的位置
        }
    }
}    

擴展KMP算法小記