KMP算法----講解篇

衆所周知,KMP算法是字符串匹配算法。
那麼首先,我們來看看一般字符串匹配算法的過程:

一般字符串匹配算法(暴力匹配)

如圖。在這裏插入圖片描述
兩個字符串T和P,我們傳統做法便是這樣:

1.首對齊,一個一個對比,碰到了不匹配的:
在這裏插入圖片描述
2.待檢測字串往後移一個,再重複同樣的事情:
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

到現在,我們用一般匹配算法(暴力匹配)完成了這個字符串匹配過程。

我們總結一下一般匹配算法:暴力算法匹配過程中,我們首先會對T[0]和P[0]進行比較,如果相同則匹配下一位,知道出現不相同的情況,此時我們會丟棄到已經匹配的情況,重新讓p[0]和T[1]對齊進行匹配,重複上述過程,知道匹配成功或主串遍歷結束。我們發現,這種過程很繁瑣,這種效率很低

舉個例子:
在這裏插入圖片描述
如圖,主串是aaaaaaaaaab,模式串是aaaab,用一般匹配算法的話,在與主串中每一位對齊時都要判斷五次,那麼總共的判斷就需要35次匹配(自己手動模擬一下哦)。

————————————————手工分割線——————————————
爲了提高算法的效率,我們對字符串一般暴力匹配法進行優化,這樣就遇到了我們今天的主角。

KMP算法

在這裏插入圖片描述
首先,在看KMP之前,我們需要知道一個叫前綴表的東西。前綴表究竟是什麼呢?拿這個模式字符串P來說吧。

  • 首先,我們看看前綴是什麼?
    如P:a b a b c所有的前綴:
    a
    a b
    a b a
    a b a b
    a b a b c
    像這樣,從頭開始,在字符串中某一位置停止的子字符串,就是前綴
    同理,後綴就是從後開始,在字符串中某一位置停止的子字符串
    P:a b a b c所有的後綴:
    c
    b c
    a b c
    b a b c
    a b a b c
  • 其次,我們要對前綴中的每個前綴求最長公共子前後綴。
    a------------沒有子前後綴,所以爲0
    a b----------最長是一個元素:a和b,不是公共的。因此,最長公共子前後綴也是0
    a b a--------首先最長是兩個元素:a b 和b a 是最長的,但不是公共的。所以我們看一個元素的:a和a,好了,最長公共子前後綴是1
    a b a b-----首先最長是三個元素,明顯,a b a和b a b 不是公共的,所以減少一個元素:a b和a b,好了,最長公共子前後綴是2
    a b a b c----首先最長是四個元素,明顯,abab和babc不同。所以減少一個元素:aba 和abc 同樣不是公共的。再減少一個元素 :ab和bc,也是非公共的。再減少:a和c,也是非公共。因此,這個串的最長公共子前後綴是0
    好,到現在,我們求出來了這個前綴表每行的最長公共前後綴:
    a --------------0
    a b ------------0
    a b a----------1
    a b a b -------2
    a b a b c------0
  • 最後,我們的前綴表就是去除最後一行,再往前方增加一個-1:
    在這裏插入圖片描述

把綠色這一段,結合模式串P,像這樣寫出來:

a b a b c
-1 0 0 1 2

這個表,就是前綴表。
———————————————手工分割線——————————————

現在,我們看看如何用剛剛求得的前綴表運行KMP算法。

在這裏插入圖片描述

注:紅色是模式串的下標,藍色就是我們剛纔所求的前綴表裏的數據。

1.進行第一次匹配。

.
在這裏插入圖片描述
T[3]和P[3]匹配失敗。
匹配失敗的時候,我們就要看前綴表,當前導致匹配失敗的是P[3],前綴表顯示的是1,所以我們把P[1]和當前匹配失敗的T[3]位置對齊。
在這裏插入圖片描述
對齊之後如上圖。

2.進行第二次匹配。

上一步做完之後,當前是T[3]和P[1]對齊,那麼現在就由當前對齊位置開始,逐步往後匹配。 也就是說,在當前對齊位置之前的元素,不需再對它進行匹配了(原因會在後面解釋,現在就關注KMP的過程)。
在這裏插入圖片描述
發現此時匹配失敗,根據上面的方法,此時前綴表顯示的0,所以把P[0]與匹配失敗的位置對齊。
在這裏插入圖片描述
對齊之後如上圖。

3.同2,由當前對齊位置開始,逐步往後匹配。

.
在這裏插入圖片描述
匹配失敗,此時前綴表顯示0,繼續把P[0]與匹配失敗的位置對齊。
在這裏插入圖片描述
對齊之後如上圖。

4.同上,從當前對齊的位置開始,逐漸往後匹配。

.
在這裏插入圖片描述
匹配失敗,此時發現前綴表顯示的-1,那麼聰明的我們就知道,現在需要把P[0]的前一位與匹配失敗的位置對齊。
在這裏插入圖片描述
對齊之後如上圖所示。

5.從當前匹配失敗位置開始,逐步往後匹配。

我們發現,對齊的位置是P[-1],這要怎麼對比。很簡單呀,我們跳過它從下一位開始嘛。
在這裏插入圖片描述
此時,全部匹配成功了,此時,KMP程序結束。

————————————————手工分割線——————————————

現在,我們拿出剛纔的極端例子:
在這裏插入圖片描述

第一步:計算前綴表;
a ------------0
a a ----------1
a a a--------2
a a a a-------3
a a a a b----0
前綴表:

a a a a b
-1 0 1 2 3

第二步:進行匹配;

.
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

到此結束,我們統計一下,總共對比了19次,相比之前的35,有了很大的優化。
————————————————手工分割線——————————————

到現在爲止,我們已經感受了整個KMP算法的過程。

現在,我們來總結一下
KMP算法的基本思路:一般情況下,當字符串匹配失敗之後,我們會讓模式串後移一格,之後重頭再做匹配。而KMP算法則利用到了匹配失敗後產生的信息(當前前綴表中顯示的數字)。當匹配失敗之後,KMP算法便會知曉,某個前綴不需要再次進行匹配操作(例如上述極端例子中的前綴:a a a( P[0]P[1]P[2] ))。因爲它們已經是匹配好的。然後利用前綴表,讓當前前綴表所指示下標數字的模式串元素與當前匹配失敗位置對其,從當前位置開始,逐步往後進行匹配。(可能後面這句話有些拗口,難懂。通俗一點就是:當前位置匹配失敗了,我就找現在前綴表指向的模式串元素,以它爲準,與主串對齊,再從此模式串元素處開始,逐個往後匹配)

通過上面一段話,我們已經瞭解到了前綴表的具體作用了。但是肯定會有疑問,爲什麼前綴表具有這樣的作用呢?也就是對前綴表的原理會一頭霧水。本人才疏學淺,僅有自己的一點點感受,如下提及,望各位大佬指教!
我們不妨來深層跟蹤一下前綴表的使用過程:
在這裏插入圖片描述
如圖,我們再當前位置失配。當前前綴表是1。這個1,就是串(a b a)的最大公共前後綴:
在這裏插入圖片描述
串1(a)=串2(a);我們發現,因爲能走到失配位,所以失配位之前都是匹配的。當前失配位的上一位是匹配的,而上一位又和P[0]相同,所以P[0]不需要匹配,只需要從P[1]往後遍歷。
這個例子可能不太好理解,相信看了下一個例子,一定會對前綴表有所理解的。

再舉個例子:

在這裏插入圖片描述
當前前綴表顯示是3,也就是串(a a a a)的最長公共子前後綴是3(如圖):
在這裏插入圖片描述
我們來分析:
在這裏插入圖片描述
棕色串A和紫色串B是當前失配位所示(3)對應的最長公共子前後綴。當然這兩個串(A、B)肯定是相同的啦(這是最大公共子前後綴嘛)。那麼因爲我們已經對比到了P[4],那麼之前的都是匹配成功的了,那便是說,藍色串C和紫色串B完全相同。咦!是不是發現了什麼:串A和串B是相同的,串C和串B完全相同,那麼A=B=C,所以A串和C串也就匹配了呀!咦!這樣我們就不需要再匹配這這串了,所以讓P[3]和剛纔的失配位進行對齊,再從P[3]開始,往後匹配判斷就行了啊!
在這裏插入圖片描述

好了,以上就是本人對KMP淺顯的理解,有不足之處希望大佬指正。
還有下篇:KMP算法----代碼實現篇;希望各位大佬親臨指正。

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