程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> 淺談KMP算法及其next[]數組,淺談kmp算法next

淺談KMP算法及其next[]數組,淺談kmp算法next

編輯:C++入門知識

淺談KMP算法及其next[]數組,淺談kmp算法next


KMP算法是眾多優秀的模式串匹配算法中較早誕生的一個,也是相對最為人所知的一個。

算法實現簡單,運行效率高,時間復雜度為O(n+m)(n和m分別為目標串和模式串的長度),比蠻力算法的O(nm)快了許多。

理解KMP算法,關鍵是理解其中的精髓——next[]數組。

 

(統一起見,下文將目標字符串記作obj,將模式字符串記作pattern,這與後面的程序代碼是一致的)

我們給一個字符串S定義一個next值,記作next(S),next(S)=n表示:

(1)S的前n個字符構成的前綴,和後n個字符的後綴是相等的

(2)n是滿足(1)條件的極大值。

如果不存在一個滿足(1)條件的n,那麼記next(S)=0。

例如,字符串“GACC”的next值為0,“GACCGG”的next值為1(極大前(後)綴為“G"),

“GACCGGACC”的next值為4(極大前(後)綴為”GACC“),“GAGAG”的next值為5。(極大前(後)綴就是它本身)。

而next[]數組,記錄的則是pattern的所有前綴的next值。

 

next數組的作用,就是在模式匹配的過程中,盡可能減少模式串的偏移量。

下令obj=”GACGAACGACCGACGACGCCGACGAC“(O__O"…),pattern=”GACGCCG“。

模式匹配的流程如下:

設立兩個”游標“i和j,分別指向obj串和pattern串當前正在檢查的字符,初始時令i=j=0。

首先檢查第一個字符,若obj[i]==pattern[j],那麼i++,j++。直到遇到obj[i]!=pattern[j]的情形。

GACGAACGACCGACGACGCCGACGAC

GACGCCG

前4個字符檢查通過,但第5個字符(i=j=4)出了問題。

樸素的做法是讓i和j都回退,對於此例,回退至i=1,j=0,然而這樣無疑會做許多重復的計算。

而KMP算法的做法則是:只移動pattern。而移動的方法,則關系到算法的效率甚至正確性。

我們的原則是:保證正確的前提下,使得重復判斷的次數盡可能少。

最佳的做法是將j移動到next[j-1]的位置。

(1)由next的性質(前綴等於後綴的極大值)可知,這樣可以省去盡可能多的判斷

(2)並且可以保證這樣做是正確的

此時j=4,而next[j-1]=next[3]=1,所以令j=1,再次進行比較。

GACGAACGACCGACGACGCCGACGAC

------GACGCCG

比對通過。令i=5,j=2。

GACGAACGACCGACGACGCCGACGAC

------GACGCCG

next[1]=0,所以令j=0。

GACGAACGACCGACGACGCCGACGAC

----------GACGCCG

即使回退到第一位也沒有出現字符相等的情形。而我們已經無法回退了。

此時只能令i++,表示前面的部分檢查無果,繼續向後檢查。

(你也可以理解成,讓obj相對於pattern運動)

GACGAACGACCGACGACGCCGACGAC

--------------GACGCCG

重復上述的過程到了這裡(i=10,j=3),next[2]=0

GACGAACGACCGACGACGCCGACGAC

--------------------GACGCCG

pattern無法回退了,所以向前檢查。

GACGAACGACCGACGACGCCGACGAC

----------------------GACGCCG

i=15,j=4,next[3]=1

GACGAACGACCGACGACGCCGACGAC

----------------------------GACGCCG

回退之後比對成功,i++,j++,重復這一過程,直到……

 

GACGAACGACCGACGACGCCGACGAC

 

----------------------------GACGCCG

至此我們便利用next數組完成了模式串匹配的過程。

可以看到,next數組使得匹配過程中少了很多不必要的計算,整個匹配過程顯得高效利落。

 

那麼問題來了,怎樣高效地求next數組?暴力是肯定行不通的。

事實上,next數組的求法和上述KMP算法的步驟驚人地相似,甚至可以說,求next數組的過程就是一個自我匹配的過程。

也就是:求next[j],就是讓pattern[0..j]和pattern[0..x]進行上述的匹配過程。這裡x=next[j-1]。next[j]=能夠匹配成功的子串的最大長度。

下令pattern=”GACCGGACCGA“

首先令next[0]=0,易知next[1]=0。

之後,由於pattern[2]!=pattern[0],所以next[2]=0。同理next[3]=0。

由於pattern[4]==pattern[0],所以next[4]=0+1=1。

這裡的0是next[3]的值。表示字符pattern[4]可以接到next[3]對應的後綴之後。

由於pattern[5]!=pattern[1],pattern[5]==pattern[next[0]]==pattern[0],所以next[5]=1。

詳情如下:

GACCGG

--------GA    【 用pattern[0..1]匹配pattern[0..5],這裡的1是next[4] 】

匹配不通過,所以令”模式串“的游標移至next[1]=0處(加了引號是因為這裡的”模式串“是對pattern本身進行匹配)

GACCGG

----------GA

類似地,可得:next[6]=next[5]+1=2,next[7]=next[6]+1=3,next[8]=next[7]+1=4,next[9]=next[8]+1=5。

next[10]的求法如下:

GACCGGACCGA

----------GACCGG    【 next[4]=1 】

 

GACCGGACCGA

------------------GACCGG

故next[10]=2。

 

代碼如下:

1 #include <cstdio> 2 #include <cstring> 3 4 const int maxL=1000005; 5 6 char obj[maxL]; 7 char pattern[maxL]; 8 9 void input() 10 { 11 scanf("%s%s",obj,pattern); 12 } 13 14 int next[maxL]={0}; 15 16 int getNext() 17 { 18 next[0]=0; 19 int len=strlen(pattern); 20 for(int i=1;i<len;i++) 21 { 22 int k=next[i-1]; 23 while(k>=0 && pattern[i] != pattern[k]) k=k?next[k-1]:-1; 24 next[i]=k+1; 25 } 26 return len; 27 } 28 29 int kmp() //searching for the first place that pattern appears in obj 30 { 31 int lenObj=strlen(obj); 32 int lenPattern=getNext(); 33 for(int i=0,j=0;i<lenObj;i++) 34 { 35 while(j>=0 && obj[i] != pattern[j]) j=j?next[j-1]:-1; 36 if(lenPattern==(++j)) return i-j+1; 37 } 38 return -1; //not found 39 } 40 41 int main() 42 { 43 input(); 44 printf("%d\n",kmp()); 45 return 0; 46 } View Code

(我們用-1作為循環終止的條件)

附一道簡單的練習題,求pattern在obj中出現的次數。

1 #include <cstdio> 2 #include <cstring> 3 4 const int maxL=1000005; 5 6 char obj[maxL]; 7 char pattern[maxL]; 8 9 void input() 10 { 11 scanf("%s%s",pattern,obj); 12 } 13 14 int next[maxL]={0}; 15 16 int getNext() 17 { 18 next[0]=0; 19 int len=strlen(pattern); 20 for(int i=1;i<len;i++) 21 { 22 int k=next[i-1]; 23 while(k>=0 && pattern[i] != pattern[k]) k=k?next[k-1]:-1; 24 next[i]=k+1; 25 } 26 return len; 27 } 28 29 int kmp() 30 { 31 int ans=0; 32 int lenObj=strlen(obj); 33 int lenPattern=getNext(); 34 for(int i=0,j=0;i<lenObj;i++) 35 { 36 while(j>=0 && obj[i] != pattern[j]) j=j?next[j-1]:-1; 37 if(lenPattern==(++j)) { ++ans; j=next[j-1]; } 38 } 39 return ans; 40 } 41 42 int main() 43 { 44 int T; scanf("%d",&T); 45 while(T--) 46 { 47 input(); 48 printf("%d\n",kmp()); 49 } 50 return 0; 51 } Problem:POJ P3461

 

算法這東西,難者不會,會者不難。如果理解時遇到了瓶頸,不妨用筆實驗一下,總結規律,也許就能悟出算法的思想。

ps.推薦另一篇博文:http://blog.csdn.net/OIljt12138/article/details/51107585,可以作為閱讀本文的對照參考

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved