概述:本文主要在理論層面上分析KMP的基本實現原理以及《部分匹配表》推導過程;不涉及代碼實現;如果您對KMP的實現代碼(OC)實現感興趣,可參考:
KMP(一) 模式匹配算法推導 --《部分匹配表》
KMP(二) 模式匹配算法實現
KMP(三) 字符串快速匹配示例
一: KMP主要解決的問題:
KMP是三位大牛:D.E.Knuth、J.H.Morris和V.R.Pratt同時發現的。其中第一位就是《計算機程序設計藝術》的作者!!
KMP算法要解決的問題就是在字符串(也叫主串,下文統一稱"P")中快速高效匹配子串(下文統一稱"S")并確定S在P中位置的問題。說簡單點就是我們平時常說的關鍵字(詞)搜索。
二. 字串樸素匹配算法實現:
看一個樸素匹配算法示例:
主串: P="BBC ABCDAB ABCDABCDABDE" 長度為Length_p
字串: S = "ABCDABD" 長度為Length_s
首先,字符串"BBC ABCDAB ABCDABCDABDE"的第一個字符與搜索詞"ABCDABD"的第一個字符,進行比較。因為B與A不匹配,所以搜索詞后移一位。
因為B與A不匹配,搜索詞再往后移。
就這樣,直到字符串有一個字符,與搜索詞的第一個字符相同為止。
接著比較字符串和搜索詞的下一個字符,還是相同。
直到字符串有一個字符,與搜索詞對應的字符不相同為止。
這時,最自然的反應是,將搜索詞整個后移一位,再從頭逐個比較。這樣做雖然可行,但是效率很差,因為你要把"搜索位置"移到已經比較過的位置,重比一遍,很明顯P的位置在回溯。分析樸素匹配算法時間復雜度為:
O((Length_p - Length_s + 1) * Length_s)
為方便后續說明,我們來約定一組表達規則:
主串 P 和字串 S 中有以下表達,示例如:
P[0] = "B"
P[4] = "A"
P[0~5]="BBC AB"
S[0] = "A"
S[2]="C"
S[1~3]="BCD"
我們觀察一下此時字串 S 和 主串P:
以下重要:
當S[0~5] = P[4~9] 并且 S[6] != P[10]時,S[0~3] 內沒有重復的字符,而切S[0~3] =P[4~7],所以在進行下一次比較時,我們可以直接將字串S移動至主串P的P[8] 位置開始比較,同時保持P的上一次查找位置不變(P[10]), 而不是像上圖一樣從P[5]開始循環比較,因為此時的P[5~8] 不可能與S(主要看S[0~3])匹配 ;這樣效率會大大提升;接下來看KMP是如何做的;
三. KMP算法示例分析:
KMP核心原則: 保持主串P上次查找位置(index_p)不回溯,移動字串S到合適的位置(index_s),下次比較起始位置分別為P[index_p]和S[index_s],不在比較P[0~index_p] 和 S[0~index_s],從而達到簡化匹配的計算過程;具體示例如下:
這里要引入一個《部分匹配表》概念;部分匹配表推導過程稍后做解釋;我們要做的先學會如何利用《部分匹配表》去實現KMP模式匹配,從而理解KMP的匹配過程;字串移位計算公式如下:
移動位數(S) = 已匹配的字符數 - 對應的部分匹配值
接著上述利用《部分匹配表》匹配分析:
7.
已知空格與D不匹配時,前面6個字符"ABCDAB"是匹配的。查表可知,最后一個匹配字符B對應的"部分匹配值"為2,照上面的公式算出向后移動的位數:
因為 6 - 2 等于4,所以將S向后移動4位。
8.
因為空格與C不匹配,搜索詞還要繼續往后移。這時,已匹配的字符數為2("AB"),對應B的"部分匹配值"為0。所以,移動位數 = 2 - 0,結果為 2,于是將搜索詞向后移2位。
9.
因為空格與A不匹配,而S已經到達最左端,故P繼續后移一位。
10.
逐位比較,直到發現C與D不匹配。于是,移動位數 = 6 - 2,繼續將搜索詞向后移動4位。
11.
逐位比較,直到搜索詞的最后一位,發現完全匹配,于是搜索完成。如果還要繼續搜索(即找出全部匹配),移動位數 = 7 - 0,再將搜索詞向后移動7位,這里就不再重復了。
四. 《部分匹配表》計算過程
首先,要了解兩個概念:"前綴"和"后綴"。 "前綴"指除了最后一個字符以外,一個字符串的全部頭部組合;"后綴"指除了第一個字符以外,一個字符串的全部尾部組合。注意:此處有個組合的概念,表達不同于KMP 算法實現中的前綴和后綴
"部分匹配值"就是"前綴"和"后綴"的最長的共有元素的長度。以"ABCDABD"為例,
1. "A"的前綴和后綴都為空集,共有元素的長度為0;
2. "AB"的前綴為[A],后綴為[B],共有元素的長度為0;
3. "ABC"的前綴為[A, AB],后綴為[BC, C],共有元素的長度0;
4. "ABCD"的前綴為[A, AB, ABC],后綴為[BCD, CD, D],共有元素的長度為0;
5. "ABCDA"的前綴為[A, AB, ABC, ABCD],后綴為[BCDA, CDA, DA, A],共有元素為"A",長度為1;
6. "ABCDAB"的前綴為[A, AB, ABC, ABCD, ABCDA],后綴為[BCDAB, CDAB, DAB, AB, B],共有元素為"AB",長度為2;
7. "ABCDABD"的前綴為[A, AB, ABC, ABCD, ABCDA, ABCDAB],后綴為[BCDABD, CDABD, DABD, ABD, BD, D],共有元素的長度為0。