KMP演算法及python實現

2021-10-08 15:48:53 字數 4401 閱讀 7134

​ kmp演算法是一種在字串匹配中應用十分廣泛、也十分高效的演算法,就是查詢模式串(子串)在目標串(主串)**現的位置,具體的問題可參考leetcode「28.實現strstr()」,題面如下圖所示。

​ 最暴力的演算法就是:模式串的第0位與目標串的第0位進行比較,如果匹配,則比較模式串的第1位與目標串的第1位;如果不匹配,則將模式串整體後移1位,比較模式串的第0位與目標串的第1位。以此類推,此處便不再贅述。這種演算法的時間複雜度為o(n * m),其中n、m分別為目標串和模式串的長度。

​ 而kmp演算法的改進之處是:匹配過程出現字元必相等的情況時,模式串不是向後移1位,而是後移一段距離再進行比較,也許這裡第一次接觸kmp演算法的旁友還比較疑惑,且聽我細細道來~

​ 假設目標串為"gtgtgagctggtgtgtgcfaa",模式串為」gtgtgcf「,如下圖所示。

​ 首先,進行逐一比較,當匹配到第6位時,出現了「壞字元」,目標串與模式串不匹配,如下圖所示。

​ 按照暴力做法,我們應該將模式串整體往後移1位,再進行逐一字元的比較;而kmp演算法,則是直接將模式串整體往後移2位,再繼續目標串當前位(仍為目標串的第5位a)與模式串相應位(模式串後移2位後,目標串的第5位對應模式串的3位t)的比較,如下圖所示。

​ 那為什麼kmp演算法可以使模式串直接後移一段距離呢?因為這裡面存在乙個簡單的邏輯:出現壞字元(即目標串的當前位)時,想要使壞字元匹配成功,則必須要求目標串當前位的前k位模式串的前k位相同時,才可能出現匹配的情況,所以可以直接移動一段距離使得這兩個片段對齊。而移動的距離取決於目標串的最長可匹配字尾子串最長可匹配字首子串的長度,如下圖所示。

​ 那如何去找到乙個字串的最長可匹配字首子串和最長可匹配字尾子串呢?需要每一輪都重新遍歷嗎?非也,用next陣列就可以確定模式串移動的距離。

​ 在使用kmp演算法進行目標串(主串)s和模式串t的匹配時,next陣列是該演算法的關鍵。依然用之前的例子,目標串s為「gtgtgagctggtgtgtgcfaa",模式串t為「gtgtgcf」。

​ next[i]的值是模式串第0位到第i-1位最長的相等的字尾子串與字首子串的長度,這樣說可能比較拗口。其實在這個案例中,當i=3時,模式串第0位到第i-1位為gtg,則最長的相等的字尾子串與字首子串為g,next[i]=1;當i=4時,模式串第0位到第i-1位為gtgt,則最長的相等的字尾子串與字首子串為gt,next[i]=2;當i=5時,模式串第0位到第i-1位為gtgtg,則最長的相等的字尾子串與字首子串為gtg,next[i]=3;當i=5時,模式串第0位到第i-1位為gtgtgc,不存在相等的字尾子串與字首子串,next[i]=0。

模式串字元gt

gtgc

fnext陣列下標i01

2345

6陣列元素值next[i]00

0123

0​ next陣列本質上可以理解當模式串的第i位目標串的當前位不匹配時,可以直接用模式串第next[i]位去和目標串的當前位進行匹配。以下圖為例:

​ 模式串的第5位c與目標串的當前位a不匹配時,直接用模式串的第next[5]位(即第3位)t與目標串的當前位a進行匹配,相當於模式串整體後移了2位,如下圖所示。

​ 之後同理,模式串的第3位t與目標串的當前位a任然不匹配,直接用模式串的第next[3]位(即第1位)t與目標串的當前位a進行匹配,相當於模式串整體又後移了2位,如下圖所示。

​ 之後的步驟依次類推。那next陣列又該如何去生成呢?

​ 可以採用類似「動態規劃」的方法,用i表示待填充的next陣列下標,用j表示將要填入next[i]的值,即最長可匹配字首子串的下乙個位置。

​ 當i=0,j=0時,next[0]=j=0。

​ 然後讓i加1,當i=1時,不存在最長可匹配字首子串,則next[1]=j=0。

​ 繼續讓i加1,當i=2時,模式串中pattern[i-1] != pattern[j],說明最長可匹配字首子串仍然不存在,next[2]=j=0。

​ 繼續讓i加1,當i=3時,模式串中pattern[i-1] == pattern[j],則j=j+1=1,next[3]=j=1。

​ 繼續讓i加1,當i=4時,模式串中pattern[i-1] == pattern[j],則j=j+1=2,next[4]=j=2。

​ 繼續讓i加1,當i=5時,模式串中pattern[i-1] == pattern[j],則j=j+1=3,next[5]=j=3。

​ 繼續讓i加1,當i=6時,模式串中pattern[i-1] != pattern[j],這裡要詳細分析:pattern[5]和pattern[3]不相等了,即最長可匹配字首子串被中斷了,j應該要回溯到之前的某乙個值,那麼要返回到什麼值呢?根據之前所說的匹配要符合的原則,j就應該返回到模式串第j位前的最長可匹配字首子串的下一位,即j=next[j]。在本案例中,當i=6,j=3時,模式串的第0到j=3位「gtgt」和模式串的第i-j-1=2到i-1=5位「gtgc」不匹配了,那麼應該使j=next[j]=1,用模式串的第0到j=1位「gt」嘗試去和模式串的第i-j-1=4到i-1=5位「gc」進行匹配。但是回溯後,pattern[j] != pattern[i-1],則應該再次回溯使j=next[j]=0,但pattern[j] != pattern[i-1],可是無法回溯了,就使得next[6]=j=0。(這一塊的確比較燒腦,如果沒看懂,可以多看幾遍)

​ 以下是實現leetcode 28.實現strstr()的**,需要注意的是當模式串為空字串時,應該輸出0。該演算法的時間複雜度為o(n + m),其中n、m分別為目標串和模式串的長度。

class

solution

:def

strstr

(self, hayback:

str, needle:

str)

->

int:

if needle =="":

return

0next

= self.getnext(needle)

i, n, j, m =0,

len(hayback),0

,len

(needle)

while i < n and j < m:

while j >

0and hayback[i]

!= needle[j]

: j =

next

[j]if hayback[i]

== needle[j]

: j +=

1 i +=

1if j == m:

return i - j

return-1

defgetnext

(self, needle)

: i, j, l =2,

0,len(needle)

next=[

0]* l while i < l:

while j >

0and needle[i-1]

!= needle[j]

: j =

next

[j]if needle[i-1]

== needle[j]

: j +=

1next

[i]= j

i +=

1return

next

以上部分內容引自

KMP演算法及python實現

knuth morris pratt kmp 演算法是解決字串匹配問題的經典演算法,下面通過乙個例子來演示一下 給定字串 bbc abcdab abcdabcdabde 檢查裡面是否包含另乙個字串 abcdabd 從頭開始依次匹配字元,如果不匹配就跳到下乙個字元 直到發現匹配字元,然後經過乙個內迴圈...

Python程式設計KMP匹配演算法及實現

kmp演算法 字串匹配的一種基本演算法,比如 在bab abcaabcda中匹配字串abcd 即在母串中檢視是否包涵字串 對字串abcd進行檢索 第一步 發現a與b不匹配則,把a向後移動一位 第二步 發現第乙個字元a匹配,再尋找第二個字元b也匹配,再尋找第三個字元c不匹配,按照往常的做法將字串abc...

KMP演算法(Python實現)

關於kmp演算法的原理等請參閱這篇文章 kmp演算法 c 實現 本篇文章只是對kmp用python進行了實現。1.時間複雜度分析 bf演算法的時間複雜度 在最壞的情況下,bf演算法要將目標串的每乙個字元同模式串進行比較一遍,假定目標串長度為m,模式串長度為n,總的時間複雜度為o m n 而對於kmp...