1. 程式人生 > >Knuth-Morris-Pratt Algorithm(KMP演算法)探賾索隱(一)

Knuth-Morris-Pratt Algorithm(KMP演算法)探賾索隱(一)

       KMP是指Donald Knuth、 Vaughan Pratt和James H. Morris三個演算法牛人的合併簡稱,Donald Knuth就是那個寫《計算機程式設計藝術》的ACM圖靈獎得主。這個演算法是在線性時間複雜度下完成字串匹配任務,這個演算法太牛逼了,簡潔優美但十分太晦澀,充滿技巧性,給大神跪了。

問題定義:       

       字串匹配問題:在文字串S中尋找模式串W(單詞串)的位置。對應程式語言就是:在(文字)字元陣列中S[]中尋找模式字元陣列W[]的起始索引m。

暴力解法:

       很容易想到一個樸素解法,演算法複雜度為O(mn),就是遍歷整個文字字串,檢查每個字元與模式串的首字元是否相同,如果相同則繼續比對模式串後面的字母,直到所有模式串字母比對完,如果遇到不匹配則跳出迴圈從模式串首字母開始從頭檢驗。程式碼參考連結https://blog.csdn.net/To_be_to_thought/article/details/84679263

基本概念:

                                                                                     

                     的字首是子串 ,且 ,我們就說 開始於

                     的字尾是子串 ,且 ,我們就說結束於

        這裡的字首和字尾是嚴格字首和嚴格字尾,不包括原字串本身。字串的邊緣串(a border of x)是x的一個子串r,且  ),也就是說x的邊緣串既是x的字首串,又是x的字尾串,並且b是邊緣的長度。

 

                             例如: 表示空串。

                             

                             

       則x的邊緣串有 ,邊緣串 長度為0,邊緣串 長度為2。空串總是字串x的邊緣串,空串沒有邊緣串。在預處理階段,模式串的每個字首串的最長邊緣的長度是確定的。

 

重要定理:r和s都是字串x的邊緣串,並且r的長度小於s,則r也是s的邊緣串。

 

                                     

 

因此

                                             

x為字串,a是字母表裡的一個字元,如果ra也是xa的邊緣串,x的一個邊緣串r可以通過a(一個字元)延伸。

 

下面舉個例子做做匹配實驗:

文字串S: ABC ABCDAB ABCDABCDABDE

模式串W: ABCDABD

 

前三次比對如圖:

 

按照樸素的思想應該進行如下調整再比對:

 

 

 

再轉到下一步:

 

但因為已經比對過S[8]和S[9]的資訊,可以利用這個資訊直接將新的比對任務的起點移到如下圖:

 

當S[10]和W[2]發生失配時,直接將新的比對任務移動到S[11],下圖:

 

當S[17]和W[6]發生失配時,直接將新的比對任務移動到S[15],如下圖:

按照以時間換空間的思想,應該在失配時將字串的資訊記錄下來,下面來看看next陣列到底存的是什麼資訊?

       我們現在假設存在部分匹配表(partial match table)陣列T[],這個表用於告訴我們:當前匹配失敗時如何尋找預匹配起點。如果有一個以S[m]為起點(S[m+k-1])的比對任務在S[m+i]和W[i]失配時,下一個可能的比對任務可能以S[m+i-T[i]]為起點,尤其是下一個可能的位置一定在比m大的索引上(因為T[i]<i),並且我們不需要檢驗S[m+i-T[i]]到S[m+i-1]的字串是否相等,因為T[i]表明:在模式串中W[0:i-1]組成的子串的字首和字尾最長公共串長度為T[i]。

舉個例子:

                                                           

下面把這個例子構造部分匹配表的演算法流程描述一下(如圖):

i

j

j

i

j

b[i]

0

-1

 

 

 

0

 

-1

0

-1

 

 

 

1

0

0

1

0

-1

 

 

2

0

0

2

0

 

 

 

3

1

1

3

1

 

 

 

4

2

2

4

2

 

 

 

5

3

3

5

3

1

0

 

6

1

1

演算法開始 :i=0,j=-1,b[i=0]=-1
i=0<6,(j=-1<0不符合內迴圈條件)   j=0,i=1,b[i=1]=j=0 
i=1< 6,(j = 0, p[i] != p[j])  j = b[0] = -1    (j=-1<0跳出迴圈)    i=2 , j=0  b[2]=j=0
i=2<6  ( j=0>=0 , p[2]=p[0]  跳出迴圈)     i=3,j=1,b[3]=j=1
i=3<6, (j=1,p[i=3]=p[j=1]跳出迴圈)   i=4    ,   j=2  ,   b[i=4]=j=2
i=4<6 , (j=2>=0, p[i=4]=p[j=2] 跳出迴圈)    i= 5,  j=3,   b[i=5]=j= 3
i=5<6 (j=3>=0,p[i=5]=!p[j=3] )  j=b[j=3]=1  (j=1>=0,p[i=5]!=p[j=1] )   j=b[j=1]=0  (j=0>=0,p[i=5]=p[j=0]跳出迴圈)    i=6,    j=1  ,    b[i=6]=1
i=6結束迴圈

在進行運算時我們發現這個b[i=0]=-1的-1設定的恰到好處,會比b[0]=0好很多!!!

下篇敬請期待!!!

參考文獻:

https://en.wikipedia.org/wiki/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm

http://www.inf.fh-flensburg.de/lang/algorithmen/pattern/kmpen.htm