KMP字符串匹配算法
文/編輯
KMP完全匹配算法和 Levenshtein相似度匹配算法是模糊查找匹配字符串中最經典的算法,配合近期技術欄目關於算法的探討,從網上摘取了一些簡要的內容,加上自己的一些理解,向大家普及一些這方面的知識,希望能拋磚引玉。
l 算法簡介:
kmp算法是一種改進的字符串匹配算法,由D.E.Knuth與V.R.Pratt和J.H.Morris同時發現,因此人們稱它為克努特——莫里斯——普拉特操作(簡稱KMP算法)。KMP算法的關鍵是根據給定的模式串W1,m,定義一個next函數。next函數包含了模式串本身局部匹配的信息。
l KMP算法和傳統算法的匹配比較:
1. 傳統算法:
傳統匹配思想是,從目標串Target的第一個字符開始掃描,逐一與模式串的對應字符進行匹配,若該組字符匹配,則檢測下一組字符,如遇失配,則退回到Target的第二個字符,重復上述步驟,直到整個Pattern在Target中找到匹配,或者已經掃描完整個目標串也沒能夠完成匹配為止。假設模式串Pattern長度為m,目標串Target長度為n,則每次比較最多需要花費O(m)的時間,算法的復雜度為O((n-m+1)*m)。這種算法沒有利用匹配過的信息,每次都從頭開始比較,速度很慢。
2. KMP算法:
在傳統算法的基礎上,當匹配失敗后,並不簡單地從目標串下一個字符開始新一輪的檢測,而是依據在檢測之前得到的有用信息,或者說模式串本身的特征信息,取得next函數跳轉的位置,直接跳過不必要每次從頭檢測,從而達到一個較高的檢測效率。
l 算法實例說明:
假設在串S=”abcabcabdabba”中查找T=” abcabd”
1. 傳統算法:
先是比較S[0]和T[0]是否相等,然后比較S[1] 和T[1]是否相等…我們發現一直比較到S[5] 和T[5]才不等。如圖:
當這樣一個失配發生時,T下標必須回溯到開始,S下標回溯的長度與T相同,然后S下標增1,然后再次比較。如圖:
這次立刻發生了失配,T下標又回溯到開始,S下標增1,然后再次比較。如圖:
這次立刻發生了失配,T下標又回溯到開始,S下標增1,然后再次比較。如圖:
又一次發生了失配,所以T下標又回溯到開始,S下標增1,然后再次比較。這次T中的所有字符都和S中相應的字符匹配了。函數返回T在S中的起始下標3。如圖:
2. KMP算法:
還是相同的例子,在S=”abcabcabdabba”中查找T=”abcabd”,如果使用KMP匹配算法,當第一次搜索到S[5]和T[5]不等后,如圖:
S下標不是回溯到1,T下標也不是回溯到開始,而是根據T中T[5]==’d’的模式函數值(next[5]=2),直接比較S[5] 和T[2]是否相等,因為相等,S和T的下標同時增加;因為又相等,S和T的下標又同時增加。。。最終在S中找到了T。如圖:
此處關鍵的問題是,為什么KMP算法能聰明的知道應該直接從S[5] 和T[2]開始比較呢?因為它能夠利用已經得到的部分匹配信息來進行過濾。因為我們根據第一次比較得到的結果,如圖:
很明顯已經匹配了到了部分結果”abcab”,只是由於到T[5]==’d’的時候才無法和S[5]=’c’匹配上的,此時按傳統算法T[0]應該重新開始和S[1]比較,但是根據之前的匹配結果S[1]等於T[1],但很明顯T[0]不等於T[1],因此比較T[0]和S[1](也即T[1])是完全沒有必要的。
以此類推,根據已經得到的結果”abcab”,T[0]重新開始比較的時候,應該和S[3](也即T[3])開始比較才是”明智”的。
因此 next[5]=2這個2表示T[5]==’d’的前面有2個字符和開始的兩個字符相同,且T[5]==’d’不等於開始的兩個字符之后的第三個字符(T[2]=’c’).如圖:
也就是說,如果開始的兩個字符之后的第三個字符也為’d’,那么,盡管T[5]==’d’的前面有2個字符和開始的兩個字符相同,T[5]==’d’的模式函數值也不為2,而是為0。
附件為KMP算法的實現java版,有興趣的同事可以試試:
KMPMatchString.java
