KMP算法-DFA版本

一、暴力法

爲了更好地理解KMP算法,我們先來看看樸素的暴力法是如何執行的。

設待匹配的字符串爲ABABABABAC,模式串爲ABABAC,i爲字符串下標,j爲模式串下標

首先取i=0,遍歷一遍j,直到出現不匹配:

ABABABABAC

ABABAC

 然後取i=1,遍歷一遍j,直到出現不匹配:

ABABABABAC

  ABABAC

然後取i=2,遍歷一遍j,直到出現不匹配:

ABABABABAC

     ABABAC

……

不難發現,這種暴力法的缺點:做了很多無用功,從i往後已經走了很多步了,但是每次i還是隻能退回去。

二、“滑動條帶”的思想

如果把模式串想象成兩個重疊在一起的透明條帶,滑動其中一個 ,直到與另一個部分完全重合。

像這樣:(以i=2時爲例)

ABABABABAC

     ABABAC

->       ABABAC

這樣,i就不用動了,直接把j調整爲4,繼續匹配

具體“滑動”到什麼位置(把j調整到什麼位置),可以事先計算好,這就要說到DFA了

三、DFA

圖中,每個圓圈都是一個狀態,圓圈中的數字表示已經匹配好的字符個數,箭頭表示了狀態轉移的方式,箭頭上的字母表示下一個出現的字母是什麼。狀態怎麼轉移,已經通過“滑動窗口”定義好了,於是DFA也就定義好了。

可以證明,如果每一步都遵循DFA來走,那麼在任意時刻,能匹配到的字符串都是最長的。(證明:如若不然,必然存在某個時刻t,使得t-1時刻是最長的,t時刻不是最長的,那麼就違背了滑動窗口的定義)

這一點很關鍵,在接下來DFA生成的部分會用到。

現在先不管DFA是怎麼高效生成的,既然我們已經可以用笨方法(滑動窗口)來生成DFA了。

假設我們已經有了DFA,那麼代碼就可以這麼寫:

(由於i不會回退,我們可以一邊讀數據一邊處理:

可見有了DFA,還是非常高效的。i不會回退,因此可以在O(待匹配字符串長度)的時間內完成匹配。

但是,DFA的生成會不會拖後腿呢?

四、DFA的生成(核心)

原理部分,我們可以這樣理解:

首先用“一次性完美匹配”的情況來構建DFA的骨架

接下來的問題是:在狀態j(意即:已經成功匹配了j個字符,也就是模式串中下標爲0~j-1的字符),如果在下一步出現了不匹配,該跳轉到哪裏?

設待匹配字符串中,不匹配的那個字符爲c。

目標是要滑動窗口,尋找最長的重疊字符串,換言之,要在 [ 下標爲1~j-1的字符 + c ] 中匹配最長的字符串。等等,這個場景似曾相識,既然恰好j個字符(不會越界),那我們用當前的DFA半成品過一遍 [ 下標爲1~j-1的字符 + c ] ,結果指向哪,我們就跳轉到哪!

於是,生成DFA的代碼如下:

代碼中的X代表用半成品DFA過一遍 [ 下標爲1~j-1的字符 ](沒有c)的結果

複雜度如圖所示,沒怎麼拖後腿

 

PS:還有一種NFA版本的KMP算法,更爲常見一些,改天再研究

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章