[算法系列之二十六]字符串匹配之KMP算法
一 簡介
KMP算法是一種改進的字符串匹配算法,由D.E.Knuth與V.R.Pratt和J.H.Morris同時發現,因此人們稱它為克努特—莫里斯—普拉特操作(簡稱KMP算法)。KMP算法的關鍵是利用匹配失敗后的信息,盡量減少模式串與主串的匹配次數以達到快速匹配的目的。
二 基于部分匹配表的KMP算法
舉例來說,有一個字符串”BBC ABCDAB ABCDABCDABDE”,我想知道,里面是否包含搜索串”ABCDABD”?
步驟1:字符串”BBC ABCDAB ABCDABCDABDE”的第一個字符與搜索串”ABCDAB D”的第一個字符進行比較。因為字符B與A不匹配,所以搜索串后移一位。
步驟2:因為字符B與A不匹配,所以搜索串再往后移一位。
步驟3:就這樣,直到字符串有一個字符與搜索串的第一個字符相同為止。
步驟4:接著比較字符串和搜索串的下一個字符,還是相同。
步驟5:直到字符串有一個字符與搜索串對應的字符不相同為止。
步驟6:這時,最自然的反應是,將搜索串整體后移一位,再從頭逐個比較。這樣做雖然可行,但是效率很差,因為你要把”搜索位置”移到已經比較過的位置,重比一遍。(這就是之前講的[算法系列之十二]字符串匹配之蠻力匹配)
步驟7:一個基本事實是,當空格與D不匹配時,你其實知道前面六個字符是”ABCDAB”。KMP算法的想法是,充分利用這個已知信息,不要把”搜索位置”移回已經比較過的位置,繼續把它向后移。這樣就提高了效率。
步驟8:怎么做到這一點呢?可以針對搜索串,算出一張《部分匹配表》(Partial Match Table)。這張表是如何產生的,后面再介紹,這里只要會用就可以了。
步驟9:已知空格與D不匹配時,前面六個字符”ABCDAB”是匹配的。
查表可知,最后一個匹配字符B對應的”部分匹配值”為2,因此按照下面的公式算出向后移動的位數:
移動位數 = 已匹配的字符數 - 失配字符的上一位字符對應的部分匹配值因為 6 - 2 等于4,所以將搜索串向后移動4位,如下圖。
步驟10:因為空格與C不匹配,搜索串還要繼續往后移。這時,已匹配的字符數為2(”AB”),對應的”部分匹配值”為0。所以,移動位數 = 2 - 0,結果為 2,于是將搜索串向后移2位。
步驟11:因為空格與A不匹配,繼續后移一位。逐位比較,直到發現C與D不匹配。
于是,移動位數 = 6 - 2,繼續將搜索串向后移動4位。
步驟12:逐位比較,直到搜索串的最后一位,發現完全匹配,于是搜索完成。
如果還要繼續搜索(即找出全部匹配),移動位數 = 7 - 0,再將搜索串向后移動7位,這里就不再重復了。
三 部分匹配表(Partial Match Table)
下面介紹《部分匹配表》是如何產生的。首先,要了解兩個概念:前綴和后綴。
- 前綴指除了最后一個字符以外,一個字符串的全部頭部組合;
- 后綴指除了第一個字符以外,一個字符串的全部尾部組合。
舉例說明:
部分匹配值就是前綴和后綴的最長的共有元素的長度。
以”ABCDABD”為例。
由此得到部分匹配表,如下:
“部分匹配”的實質是,有時候,字符串頭部和尾部會有重復。比如,”ABCDAB”之中有兩個AB,那么它的”部分匹配值”就是2(AB的長度)。搜索詞移動的時候,第一個AB向后移動4位(字符串長度-部分匹配值),就可以來到第二個AB的位置。
四 基于next數組的KMP算法
通過以上的匹配過程可以看出,問題的關鍵就是尋找搜索串中最大長度的相同前綴和后綴。找到了搜索串中每個字符之前的前綴和后綴公共部分的最大長度后,便可基于此匹配。而這個最大長度便正是next 數組要表達的含義。
4.1:根據《部分匹配表》求next 數組
經過上面敘述我們已經知道搜索串“ABCDABD”各個前綴后綴的最大公共元素長度了,如下圖所示:
部分匹配表也由此而出如下所示:
有了部分匹配表我們就可以利用下面公式計算移動位數:
移動位數 = 已匹配的字符數 - 失配字符的上一位字符對應的部分匹配值上文利用部分匹配表和移動位數計算公式進行匹配時,我們發現,當一個字符失配時,其實沒必要考慮這個失配的字符,我們每次都是看的是失配字符的上一位字符“部分匹配值”。如此,便引出了next 數組。
把next 數組跟“部分匹配表”對比后,不難發現,next 數組相當于“部分匹配表” 整體向右移動一位,然后第一個元素值賦為-1。意識到了這一點,你會驚呼原來next 數組的求解竟然如此簡單:就是找最大對稱長度的前綴后綴,然后整體右移一位,第一個元素值賦為-1(當然,你也可以直接計算某個字符對應的next值,就是看這個字符之前的字符串中有多大長度的相同前綴后綴)。
更新一下搜索串移動位數的計算公式:
移動位數 = 失配字符的位置 - 失配字符next值其實兩公式實質上是一樣的,失配字符的位置等于已匹配的字符數,失配字符next 值等于失配字符的上一位字符的部分匹配值,只是換一種說法而已。
4.2 遞推求解next數組
對于給定的字符串p,其next數組的含義是:對于k=next[j],p的前綴p[0…k-1]和p的后綴p[j-k…j-1]匹配,k要盡可能的大,且k< j。我們可以根據上述含義寫出next的蠻力計算方法。復雜度應該是O(^2)。
換個思路,現在next[0]=-1,next[1]=0。
假設k=next[j],則p[0…k-1]=p[j-k…j-1],那么求next[j+1]有兩種情況:
- 如果p[k] = p[j],則p[0…k]=p[j-k…j],所next[j+1]=k+1=next[j]+1
- 如果p[k] != p[j],這是可以看做另外一個字符串匹配的問題,主串和模式串都是p,當匹配失敗時,k應該如何移動呢?顯然是k=next[k]
引用:
字符串匹配的KMP算法
從頭到尾徹底理解KMP
KMP算法求next數組
KMP算法的next[]數組通俗解釋
總結
以上是生活随笔為你收集整理的[算法系列之二十六]字符串匹配之KMP算法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: IOS开发基础知识--碎片8
- 下一篇: ***的有关说明