1. 程式人生 > >拓展KMP算法詳解

拓展KMP算法詳解

上一個 problem clas 長度 nbsp sizeof 分析 get 沒有

  拓展KMP解決的問題是給兩個串S和T,長度分別是n和m,求S的每一個後綴子串與T的最長公共前綴分別是多少,記作extend數組,也就是說extend[i]表示S[i,n-1](i從0開始)和T的最長公共前綴長度。

  需要註意的是如果extend[i]=m,即S[i,n-1]和T的最長公共前綴長度是m(正好是T的長度),那麽就表示T在S中找到匹配而且起始位置是i,這就解釋了為什麽這個算法叫做拓展KMP了。

  其實大致和KMP有異曲同工之妙,都是匹配,都是借用一個next數組。 

  下面舉一個例子,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。這個例子很有代表性,有興趣的讀者可以繼續計算剩下的extend數組。

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數組的定義),分兩種情況討論:

第一種情況: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數組的定義不符(next[i]表示T[i,m-1]和T的最長公共前綴長度),所以在這種情況下,不用進行任何匹配,就知道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數組即可,這裏不再贅述。

2. 時間復雜度分析

  下面來分析一下算法的時間復雜度,通過上面的算法介紹可以知道,對於第一種情況,無需做任何匹配即可計算出extend[i],對於第二種情況,都是從未被匹配的位置開始匹配,匹配過的位置不再匹配,也就是說對於母串的每一個位置,都只匹配了一次,所以算法總體時間復雜度是O(n)的,同時為了計算輔助數組next[i]需要先對字串T進行一次拓展kmp算法處理,所以拓展kmp算法的總體復雜度為O(n+m)的。其中n為母串的長度,m為子串的長度。

3. 模板

 1 #include<cstdio>
 2 #include<cstring>
 3 const int maxn = 100010;
 4 char s[maxn]= "aaaabaa", t[maxn] = "aaaaa";
 5 int nextt[maxn], ls, lt, extend[maxn];
 6 
 7 void get_next();
 8 void exkmp();
 9 
10 int main()
11 {
12     ls = strlen(s);//不要全局和局部亂用 
13     lt = strlen(t);
14     
15     exkmp();
16     for(int i = 0; i < lt; i++){
17         printf("%d ",nextt[i]);
18     }
19     puts("");
20     
21     for(int i = 0; i < ls; i++){
22         printf("%d ",extend[i]);
23     }
24     puts("");
25     return 0;
26 } 
27 
28 void get_next(){
29     nextt[0] = lt;
30     
31     int k = 1;
32     while(k < lt && t[k] == t[k - 1])
33         k++;
34     nextt[1] = k;
35     
36     int po = 1;
37     for(k = 2; k < lt; k++){
38         if(nextt[k - po] + k <  nextt[po] + po)
39             nextt[k] = nextt[k - po];
40         else{
41             int j = nextt[po] + po - k;
42             if(j < 0)
43                 j=0;
44             while(j + k < lt && t[j] == t[j + k])
45                 j++;
46                 
47             nextt[k] = j;
48             po = k;
49         }
50     }
51 }
52 
53 void exkmp(){
54     memset(nextt,0,sizeof(nextt));
55     get_next();
56     
57     int k = 0;
58     while(k < ls && k < lt && s[k] == t[k])
59         k++;
60     extend[0] = k;
61     
62     int po=0;
63     for(k = 1; k < ls; k++){
64         if(nextt[k - po] + k < extend[po] + po)
65             extend[k] = nextt[k - po];
66         else{
67             int j = extend[po] + po - k;
68             if(j < 0)
69                 j = 0;
70             while(k + j < ls && j < lt && s[k + j] == t[j])
71                 j++;
72             
73             extend[k] = j;
74             po = k;
75         }
76     }
77 }

4. 模板練習題

https://cn.vjudge.net/problem/HDU-3613

練習題參考解答:

5.參考博客原文鏈接:
https://blog.csdn.net/dyx404514/article/details/41831947

拓展KMP算法詳解