KMP算法的工作流程介紹

最近又想起了KMP算法,原來一直沒搞明白工作原理,現在總算是開點竅了,推薦大家看這篇文章,寫的很簡單易懂

推薦理由:簡單明瞭,是我看過介紹KMP算法流程的所有文章中,最易懂的一篇(這篇文章僅僅是介紹了KMP算法的工作流程,並沒有介紹KMP算法爲什麼當初這麼設計!)

原文地址:http://jakeboxer.com/blog/2009/12/13/the-knuth-morris-pratt-algorithm-in-my-own-words/

===================================================================================

最近我一直在研讀各種關於Knuth-Morris-Pratt這種字符串匹配算法的介紹文章。但種種原因導致沒有一種我能夠真正理解的簡簡單單的介紹。

 

最終,在一遍又一遍的讀完算法導論中相同段落之後,我決定靜下心來好好寫一些例子,把自己所理解的KMP算法寫出來。現在,我總算明白了這個算法,也能解釋明白它了!下面我就用我自己的大白話給大家姐是個明白!不過你需要注意一下,我並不打算解釋KMP算法爲什麼比其他的字符串匹配算法高效,因爲在這裏已經解釋的夠清楚的了,而我要做的工作是用自己的話,介紹一下KMP算法的工作流程。

 

部分匹配表(The Partial Match Table)

KMP的精髓無疑就是部分匹配表了。明不明白KMP本質上就在於明不明白部分匹配表裏所有數值的含義,我會儘可能的用簡單明瞭的話進行解釋。

下面這個是”abababca”這個模板(pattern)的部分匹配表:

char:   | a | b | a | b | a | b | c | a |

index:  | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |

value:  | 0 | 0 | 1 | 2 | 3 | 4 | 0 | 1 |

如果我有一個8個字符的模板(這裏就拿”abababca”來舉例子),我的部分匹配表會有8列。如果我此時此刻正關注於模板的第八列,即最後一列,那意味着我考慮到了整個模板,(即”abababca”);如果我此時此刻正關注於模板的第七列,那意味着我當前僅僅考慮到了整個模板的前七位,(即”abababc”),此時第八位(”a”)是無關的,不用理睬;如果我此時此刻正關注於模板的第六列,那意味着……看到這裏你應該已經明白我的意思了。目前我還沒有提到部分匹配表每列數據的含義,在這裏僅僅是先交代一下部分匹配表的大概。

 

現在,爲了解釋剛剛提到的每列數據的含義,我們首先要明白什麼是最優前綴(proper prefixes)什麼是最優後綴(proper suffixes)

最優前綴:一個字符串中,去除一個或多個尾部的字符得到的新的字符串就是最優前綴。例如,”S”,”Sn”, ”Sna”, ”Snap”,都是”Snape”的最優前綴。

最有後綴:一個字符串中,去除一個或多個首部的字符得到的新的字符串就是最有後綴。例如,”agrid”, ”grid”, ”rid”, ”id”, ”d”都是 ”Hagrid”的最優後綴。

明白了這兩個概念以後,我現在就可以用一句話概括部分匹配表裏每列數據的含義了:

模板(子模板)中,既是最優前綴也是最優後綴的最長的字符串的長度。


下面我來驗證一下這句話。還拿”abababca”這個模板舉例來講,假設當前我正在關注於模板的第三列數據,如果你還記得我在前文提到的,你應該知道這意味着我們目前僅僅關心前三個字母(”aba”)。在”aba”這個子模板有兩個最優前綴(“a”和”ab”),有兩個最優後綴(“a”和”ba”),不難看出,最優前綴與最優後綴中,相同的只有”a”這一個,那麼此時此刻既是最優前綴也是最優後綴的最長的字符串的長度就是1了。

我們不妨再試一試第四列,第四列的話我們應該是關注於前四位字母(“abab”),這裏可以看出有三個最優前綴(“a”,”ab”,”aba”)和三個最有後綴(“b”,”ab”,”bab”),這一次”ab” 既是最優前綴也是最優後綴,並且長度爲2,是最優前綴與最優後綴交集裏最長的,因此,部分匹配表的第四列的值賦值爲2。

如果你還沒試夠,那你可以再試試第五列的情況,也就是關注於前五位,即“ababa”。這裏不難得到4個最優前綴(“a”, “ab”, “aba”, and “abab”)和四個最有後綴(“a”, “ba”,“aba”, and “baba”),其中公共的有兩個(“a”和“aba”),對比一下不難看出”aba”長度爲3,且比”a”要長,所以部分匹配表的第五列賦值爲3。

跳過中間的,直接來看第七列吧,此時只考慮前七位字母(“abababc”)。即使不一一枚舉出所有的最優前綴與最優後綴也不難看出這兩個集合之間不會有任何的交集。因爲任何的最有後綴都以”c”結尾,但沒有任何最優前綴是以”c”結尾的,所以沒有符合要求的,因此第七列賦值爲0。

最後,讓我們看看第八列的情況,也就是考慮到整個模板的時候(abababca)。不難看出最優前綴與最有後綴都以”a”開頭以”a”結尾,所以第八列的值至少是1。而事實上1就是最終結果了,所有大於等於2的長度中,所有最有後綴都包含”c”,但只有”abababc”這一個最優前綴包含”c”,而七位的最優後綴”bababca”並不與其一致,所以第八列最終賦值爲1。

 

如何使用部分匹配表

在已經匹配到部分字符後,如果發現下一個字符不再與模板匹配,此時通過使用部分匹配表就可以快速的跳過一些字符,從而避免冗餘的比對提升效率。具體的使用方法可以用下面的話來解釋:

如果已經匹配到的部分字符串的長度爲partial_match_length,且字符串中下一個字符不再與模板匹配的情況下,如果table[partial_match_length] > 1那麼我們可以跳過partial_match_length- table[partial_match_length - 1]這麼多個字符。

還是拿”abababca”來舉例,如果我們用這個模板來匹配” bacbababaabcbab”的話,我們的部分匹配表應該是這樣的:

char:   | a | b| a | b | a | b | c | a |

index:  | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |

value:  | 0 | 0 | 1 | 2 | 3 | 4 | 0 | 1 |

第一次匹配的時候是這裏:

ba cbababaabcbab

  |

  a bababca

此時partial_match_length值爲1,對應的table[partial_match_length - 1] 即 table[0]爲0,所以這種情況下我們不能跳過任何字符。下一次匹配的時候是這裏:

bacb ababa abcbab

         | | | | |

         ababa bca

此時partial_match_length值爲5,對應的table[partial_match_length - 1] 即 table[4]爲3,這意味着我們可以跳過partial_match_length- table[partial_match_length - 1] ,即 5 - table[4] 即 5 - 3 亦即 2個字符。

// x 表示跳過一個字符

bacb ababa abcbab

         xx | | |

             aba babca

此時partial_match_length值爲3,對應的table[partial_match_length- 1] 即 table[2]爲1,這意味着我們可以跳過partial_match_length - table[partial_match_length - 1] ,即 3 - table[2]即 3 - 1 亦即 2個字符。

// x表示跳過一個字符

bacbababa abcbab

            xx |

                a bababca

此時此刻,模板長度大於所剩餘的目標字符串長度,所以不可能會有匹配了。

 

結論

懂了沒!正如我一開始所承諾的,沒有糾結的解釋也沒有枯燥的證明。我就是這麼理解的KMP。如果你有任何疑問或者發現我這篇文章哪裏寫錯了,請給我留言,共同成長,與君共勉!

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