以下圖所示。藍色的小三角表示和sequence比較時的開始字符,綠色小三角表示失敗後模式串比對的開始字符,紅色框表示當前比較的字符對。html
當和模式串發生不匹配時,藍色小三角後移一位,綠色小三角移到模式串的第0位。算法
若是sequence長度爲m, pattern長度爲n,暴力求解的時間複雜度:O(m * n)數組
暴力求解中"當和模式串發生不匹配時,藍色小三角後移一位,綠色小三角移到模式串的第0位。"能不能多移幾位呢?優化
在發生不匹配以前,咱們已經比較一些字符,這些字符內部有什麼能夠被咱們利用的特徵呢?spa
上圖中,pattern_1表示移動前的pattern,pattern_2表示發生了不匹配後用KMP算法移動後的pattern。code
咱們但願的效果是,既然sequence串到index_s = 10以前都已經和模式串p比較過了,儘可能不要再回退回去(退到index_s = 4的下一位即index_s = 5)從新比較了,從index_s =10繼續比較就行了。那對於模式串應該從index_p爲多少開始比較,能夠保證index_p以前的子串均與sequence中index_s 以前的子串匹配呢?htm
如上圖所示,已經匹配過的子串是sub = "ABCDAB"。看pattern串,其前綴p_part1 = 後綴p_part2. 而pattern串的後綴 p_part2 = 當前匹配的sequence串的s_part.blog
$$ p\_part1 = p\_part2 = s\_part $$get
那麼把pattern串移動到和當前匹配的sequence串後綴對應的位置上,(如pattern_2所示)就必定能夠保證 s_part = part_1,那麼從 sequence的index_s = 10 和 pattern的 index_p = 2繼續比較就能夠了。io
所以,不管在sequence的哪一個位置發生了不匹配,都不用移動當前sequence的待比較index_s,也就是尋找下一個待比較的sequence字符和pattern字符時,和sequence是解耦的。
惟一有影響的就是已經匹配過的pattern子串。對於pattern串,在index_p位置與sequence發生了不匹配,而已經匹配過的pattern的子串有 p_part1 = p_part2,所以下一次只須要用
p_part1部分的下一個元素和index_s處元素比較就能夠了。它們以前的子串必定是相同匹配的(如圖sequence和pattern_2中,s_part = p_part2)。
OK,那麼尋找已經匹配的子串的相同前綴後綴元素,以後讓pattern移動後的前綴 和 移動前的後綴對應起來,它們必定匹配,那麼繼續向後比較就能夠了。
可是有一個問題,若是已經匹配過的子串前綴後綴相同元素有多種可能的狀況,應該怎麼移動?
好比 sub = "AAABAAA"。
有3中可能的移動方式,匹配3位(sub_1),2位(sub_2)仍是1位後綴(sub_3)?
不難看出,應該匹配最長前綴後綴元素的sub_1。若是選擇了sub_2或是sub_3,則可能出現漏查。
以上例子能夠抽象出來兩個概念,一個是 前綴後綴的最長公共元素;一個是next數組。
前綴後綴的最長公共元素:已匹配過的子串的最長相同的 前綴和後綴的 元素。例子1,對於sub = "ABCDAB", 前綴後綴的最長公共元素就是"AB"。例子2,對於sub = "AAABAAA", 前綴後綴的最長公共元素就是"AAA"。也就對應圖中符號p_part1, p_part2.
next數組:由上面的例子可知,匹配失敗後尋找下一個待匹配的元素對,能夠和sequence解耦,只看pattern。在pattern的不一樣位置發生了不匹配,均可以經過pattern串自己的特徵找到下一個待匹配的元素位置。那麼能夠用一個數組記錄下,在pattern串各位發生了不匹配時候,下一個要比較的pattern的元素的位置。就是next數組。next[j] = k 表明p[j] 以前的模式串子串中,有長度爲 k 的相同前綴和後綴,即前綴 [0 ~ (k - 1)] 與 後綴 [(j - k) ~ (j - 1)] 對應匹配。
KMP算法的關鍵就是next數組的創建。
next數組初始化next[0] = -1,對於以後的值,能夠經過遞推的方法來求。已知next[0]...next[j],求next[j + 1] = ?
這裏記 next[j] = k,next[k] = k'。
next[j] = k 表示在pattern的 j 處發生了不匹配時,最長的相同前綴後綴元素長度爲 k, 即前綴 [0 ~ (k - 1)] 長度爲k的子串與 後綴 [(j - k) ~ (j - 1)] 長度爲k的子串是匹配的。
求next[j + 1],就要看在 j + 1 位置以前,最長的相同前綴後綴元素是多長,那麼p[k] 和 p[j]是否相同就很關鍵。若是兩者相同,則相比於next[j],最長的相同前綴後綴元素長度就能夠加一。若是不相同,那麼就須要從 前綴子串 [0 ~ (k - 1)] 中再截一段更短前綴,使之與後綴匹配。
(1) 若 p[k] == p[j],則next[j + 1] = next[j] + 1 = k + 1
由上圖易知,前綴的最後一位從黃色正立小三角位置移動到紅色正立小三角位置;後綴的最後一位從黃色倒立小三角位置移動到紅色倒立小三角位置。
(2) 若 p[k] != p[j],則遞推 k' = next[k]
那麼最長相同前綴後綴元素就在k處斷掉了,因此,最長相同前綴不是pattern_1.
遞推 k' = next[k] ,判斷 p[k'] ?= p[j].
(如上圖 pattern_2 黃色正立小三角及以前子串表示前綴part_1, pattern 倒立黃色小三角及以前長度爲 k' + 1 子串表示後綴part_2,它們是對應匹配的。)
a) 若p[k'] == p[j],則 next[j + 1] = k' + 1。
b) 若p[k'] != p[j],返回(2), 繼續遞推 k'' = next[k']。
以該圖爲例,仍是求next[j + 1]。如今假設 p[k] == p[j],是否就要設定 next[j + 1] = k + 1?
分析:next[j + 1]表示若是p[j + 1] != s[index_s],那麼就繼續比較 p[next[j + 1]] 和 s[index_s]。可是,若是 p[next[j + 1]] = p[k + 1] = p[j + 1],那必然和 s[index_s]不匹配。因此在最初創建next數組時候就要加入這個考慮,繼續遞推k = next[j]直到 p[j + 1] != p[k + 1]。
def getNEXT(p): len_p = len(p) NEXT = [-1] * len_p NEXT[0] = -1 # k: indicator of prefix # j: indicator of suffix k = -1 j = 0 while j < len_p - 1: if k == -1 or p[k] == p[j]: k += 1 j += 1 while p[j] == p[k]: k = NEXT[k] if p[j] != p[k]: NEXT[j] = k else: k = NEXT[k] return NEXT def KMPSearch(s, p, NEXT): i = 0 j = 0 len_s = len(s) len_p = len(p) while i < len_s and j < len_p: if j == -1 or s[i] == p[j]: i += 1 j += 1 else: j = NEXT[j] if j == len_p: return i - j else: return -1 s = "BBC ABCDAB ABCDABDABDE" p = "ABCDABD" NEXT = getNEXT(p) ret = KMPSearch(s, p, NEXT) print(ret)
參考連接:
1. 從頭至尾完全理解KMP:http://www.javashuo.com/article/p-qceswqfj-hh.html