KMP字串匹配演算法 通俗理解

小科學家發表於2009-06-12

我們從一個普通的串的模式匹配演算法開始講起,這樣你才能更深入的瞭解KMP演算法及其優點。
我們們先來看看普

通的串的模式匹配演算法是怎麼進行比較的

主串 (S) a b a b c a b c a c b a b
子串 (T)a b c a c       (子串又被稱為模式串)

紅色表示當前這趟比較指標所在位置,蘭色表示當前這趟比較中匹配的部分

第一趟(詳細過程)

a b a b c a b c a c b a b
a b c a c


a
b a b c a b c a c b a b
a b c a c


a b a b c a b c a c b a b
a b c a c
遇到不匹配的地方時指標回朔,子串向前移動一位(下同),變成如下形式

a b a b c a b c a c b a b
  a b c a c

第二趟(省略了中間階段指標移動比較過程,下同)

a b a b c a b c a c b a b
      a b c a c

第三趟

a b a b c a b c a c b a b
      a b c a
c

第四趟

a b a b c a b c a c b a b
           a b c a c

第五趟

a b
a b c a b c a c b a b
               a
b c a c

第六趟

a
b a b c a b c a c b a b
               
a b c a c _
完成匹配,跳出

這就是普通演算法的詳細匹配過程,看明白了演算法就簡單了
詳細演算法我現在就不給了,等以後有時間再編輯。不過假如串的長度為m,子串的長度為n的話,那麼這個演算法在最壞的情況下的時間複雜度為O(m*n) ,有沒有辦法降低它的時間複雜度呢?(廢話,當然有拉,不然回這個帖子幹什麼)

-----------------------------
拜D.E.Knuth 和 J.H.Morris 和 V.R.Pratt     所賜,我們有了一種時間複雜度為O(m+n)的演算法,為了紀念這3位強人為電腦科學所做的貢獻,分別取這3位先生的名字的首寫字母K,M,P來命名這個演算法,即著名的KMP演算法。

我們先不管這個KMP演算法是什麼,我們先來看看我們能夠想到怎樣的方法來改進上面的普通演算法。

通過觀察,我們發現一個問題,如果一個子串,假設為a b c d e f , 蘭色的部分與主串匹配, 紅色的f 與主串不匹配,那麼這個子串最多能往右邊移動幾位呢?因為子串中的第一個字元 a!=b !=c !=d !=e !=f ,那麼主串中與b c d e 匹配的部分肯定和a不匹配而與
f
不匹配的那部分無法判斷與a是否匹配。因此子串最多向右移動5位, 即a移動到f所在的位置上,再進行判斷。

在解決這個問題的同時,我們又發現了一個問題,當這個子串為a b c d a f
時又會如何呢?因為無法判斷主串中與a匹配對應的位置開始往後的部分是否與整個子串相匹配,所以子串最多向右移動4位,即a移動到a的位置上,然後再進行判斷。

按照這個方法,我們再來看看前面我們舉的例子。

第一趟

a b a b c a b c a c b a b
a b c a c

第二趟

a b a b c a b c a c b a b
      a b c a
c

第三趟

a
b a b c a b c a c b a b
               a b c a c
_
完成匹配,跳出

是不是簡單多了呢?這個方法不困難吧,也很容易理解吧。
事實上這就是很多人大叫困難的KMP演算法的最基本也是最核心的方法

你現在是不是在想 如果我早生50年,KMP演算法就要改名了呢?

當然,強人們的思維比我們嚴密多了,他們考慮的更完全,比如象 a b c d e a b c     ,這種字串相同的情況,當然這種情況並不比單個字元相同的情況複雜多少。在思想上KMP演算法與上面我們講的方法完全一致,都是要讓子串向右移動儘可能遠的一段距離來達到降低時間複雜度的目的,但在具體操作上KMP演算法與上面的方法又有所不同。他們為子串引入了一個引數next[ j ] ,我們先來講下next[j] 怎麼求:

假設子串為 'p1 p2 p3 p4.......pm '
對於第j個字元,有next[ j ] =  
              (1)      0       (j=1)
              (2)      Max{ k | 11...p k-1 '     = ' p j-k+1...p j-1 ' }
              (3)      1       其他情況

沒有大括號就是不方便。哎,上面的算式是不是把你弄暈了呀?沒關係,下面我介紹一種偷懶的方法

我就以

子串        a b a b a a b a b
next[j]      0 1 1 2 3 4 2 3 4

為例 來講下
next[j] 裡面 開頭的紅色01 是固定格式.

我就以蘭色的4 來說明下為什麼是4.
與4有關的, 是4所對應的蘭色a之前的所有的字元,即紫色的
a b a b a
這個字串中所有符合匹配條件的字串如下
a b a b a                a b a b a
a
                                         a
a b                                   b a (不符合,捨去)                                                                                       

a b a                             a b a       最長的匹配字串(a b a b a本身除外)在這裡 ,長度為3, 再加上1,就是4
注意next[ j ]中4 的位置!
next[ j ]中其他位置的數值也可以用同樣的方法得出

為什麼next[j]開頭為固定的01
?這個問題不太好回答。
最開頭的next[j]是由子串第一個字元前面的字元來決定的,但那不存在,因此第一個next[j]的值為0
第二個next[j]是由第一個字元決定的,由於他是自己與自己比較,一定相等,所以值為1。
這樣說可能有點牽強。那麼我們來看看下面,可能會明白些,也更加了解next[j]到底是起什麼作用的。

子串                      a b a b a a b a b
next[j]                   0 1 1 2 3 4 2 3 4
匹配子串長度       1 2 3 4 5 6 7 8 9      (對應位置     也可看成是截止到當前字元的子串串長)
最大右移              1 1 2 2 2 2 5 5 5      (當前字元不匹配時,子串最大右移距離)

我們發現,最大右移 + 對應的next[j] = 匹配子串長度,我估計這就是為什麼要引入next[ j ] 這個參量,為了方便計算最大右移。不過對於next[j]的官方具體定義我沒找到,望高人相告,在下先謝過了。

事實上,KMP演算法還是有改進餘地的,我們一直都在避免討論這樣一種情況:

a b c d e
a
0 1 1 1 1 1
a
不匹配時,能向右最大移動幾位呢?答案是6位,a 所能移動到的位置為a的下一位,即a 的右邊一位
而根據next[j]計算出子串最大右移為6-1=5。這裡他將a移動到a的位置上,然後做了一次無意義的比較(a = a     ,a 與主串不匹配,那麼顯然a 與主串也不匹配)。這個問題我就不說了,雖然我有個改進方法,但這個問題還是留給大家去思考吧。

至此,關於KMP演算法的講解就到此為止了,如果你還沒弄明白KMP是什麼,我就沒辦法了。什麼?沒給出演算法?自己翻書去,我把比演算法還重要的東西都給出來了,演算法還不是小意思
我相信即使你現在還沒明白KMP 是怎麼回事(什麼?你還沒明白?), 結合書上的演算法,你也會很快明白的。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/21359667/viewspace-606030/,如需轉載,請註明出處,否則將追究法律責任。

相關文章