KMP演算法 字串快速匹配

2021-09-11 01:43:51 字數 3427 閱讀 7478

kmp演算法的實現

最近學習了資料結構中乙個重要的內容——字串.涉及到乙個重要也是難理解的演算法——kmp演算法,即字串快速匹配模式串的演算法.在這裡寫一下心得體會,以便後來者參考.

在開始之前,還是先講一講樸素的字串匹配演算法:暴力搜尋.先講幾個基本概念:

所謂暴力搜尋,就是從主串和模式串開頭字元開始,乙個乙個匹配,匹配失敗就從主串本次匹配開頭的下一位和模式串開頭字元重新開始匹配,直到主串結束.假設主串長度為n,模式串長度為m(m ≤

\le≤ n),則暴力搜尋的時間複雜度為o(mn).當m和n比較大時,這是乙個糟糕的演算法.

暴力搜尋就不實現了,非常簡單,應該不存在問題.讓我們進入今天的正題–kmp演算法.

kmp是三個計算機大神:d.e.knuth、j.h.morris、v.r.pratt同時發現的,因此叫做kmp演算法.這是一種模式串匹配演算法.通常樸素的模式匹配演算法是一一查詢,最壞的時間複雜度為o(mn),m、n為主串、模式串的長度.kmp演算法則是線性優化,平均時間複雜度為o(m+n).

如果我們將匹配過程看成兩個指標(乙個指向主串,乙個指向模式串)的移動過程,則:

暴力搜尋匹配失敗時相當於兩次回溯(主串+模式串回溯,因此時間複雜度是做乘法:mn).

而kmp的想法是,匹配失敗了主串指標不進行回溯,而是通過一些"奇(bian)技(tai)淫(ji)巧(shu)"來決定模式串指標的回溯行為.從而使得時間複雜度做加法:m+n.這個技巧就是:字首表(prefix table).

在開始講字首表之前,這裡需要提一下,字串的前字尾和公共前字尾:

比如字串aabaa,它的所有前字尾和公共前字尾如下表:

字首字尾

公共前字尾aa

\checkmark

✓aaaa✓

\checkmark

✓aab

baaaaba

abaa

因此我們知道aabaa的最長公共前字尾是aa,這樣就可以求出字首表了,也就可以接觸到kmp的核心內容了.

kmp演算法的精髓在於:對於模式串的每乙個字首,求其最長公共前字尾的長度,構成字首表,從而確定模式串指標回溯的行為規律,下圖演示了求字首表的過程.

要回答著兩個問題,實際上就要弄清楚乙個問題–字首錶能幹什麼?這個問題的答案將在下面通過演示說明:

下圖模擬了kmp模式串匹配的具體過程:

看到這裡我們可以得出prefix table的作用了:

至此很多人一定會有疑問,最大的乙個問題大概是:為什麼根據prefix table的值就可以匹配了?這是巧合嗎?

這不是巧合,更不是上天的安排,而是一切都在我們的掌控之中啊!

這正是數學之美啊(編不下去了).

好的廢話不多說,讓我們看看字元失配的時候,會發生什麼:

那失配了,我們該幹嘛呢?當然是移動模式串指標啊!那麼如何移動最優呢?

首先,為作區分,我們將最長公共前字尾(字首)稱為最長公共字首,最長公共前字尾(字尾)稱為最長公共字尾

.顯然移動必然會帶來損失(移動必然會造成從前匹配的字首串會部分甚至全部失配),那麼為了最大限度降低這個損失,我們應該對匹配的字首串作如下移動:

將該字首串的最長公共字首移動到和它的最長公共字尾重合的位置.

可以從以下方面證明(非嚴格證明):

再想想prefix table的內容,若將其看成乙個長度為模式串長的陣列,命名prefix,則:

prefix[i]表示模式串第i個字元之前的整個字首串最長公共前字尾的長度.

為什麼是第i個字元之前的整個字首串?

因為在生成prefix的時候我們在前面補-1並將每乙個最長公共前字尾的長度依次往後推了乙個啊!因此prefix[i]實際上表示的是[0,i-1]字首串的最長公共前字尾的長度

prefix[0]實際上是當做結束標誌處理的,當回溯到prefix[0],實際上模式串就不能回溯了,因此用-1來表示最終的結束回溯狀態.

這樣我們就回答了"字首表"小節的兩個問題.

另外,還有乙個特別巧妙的細節:

至於為什麼,因為陣列下標從0開始啊!還不懂?自己好好想想吧.

說完了原理,再說實現,就兩個函式:prefix_table(args)kmp(args);用來求prefix以及匹配主串和模式串.

實現如下:

void

prefix_table

(char

*mo,

int*prefix)

的最長公共前字尾長度;

prefix[0]

=-1,prefix[1]

=0;while

(i < len)

的最長公共前字尾應在之前的基礎上加1;

prefix[

++i]

=++maxlen;

}//或者當maxlen = -1,說明已經無路可退,此時最長公共前字尾設為0,並讓i往下移一位;

else

maxlen = prefix[maxlen]

;//否則回溯到前乙個字首繼續匹配;}}

intkmp

(char

*mo,

char

*str,

int*prefix)

return cnt;

}

對於這個實現,注釋都寫得很清楚了,講幾個注意點:

其它的不多講,**興許稍微難以理解,但弄懂了原理,相信完全掌握實現也只是時間問題而已.

需要注意的是,kmp是乙個允許重疊匹配的演算法

,如在aaaaaa中匹配aa,一共能匹配上5次,每次匹配上的部分如下:

**弄懂了就去做乙個裸題試試吧:hdu-1686,這年頭這麼裸的題已經很少見了?

KMP演算法 字串的快速匹配

先了解一下字串匹配的樸素模式匹配 b f演算法 當模式串中x匹配到字串z時,比較不等,則模式串右移一位,此時指標也跟著回到模式串的初始位置 如下圖所示 這個指標的移動叫做回溯。為了消除這種回溯,提高執行效率,於是有了kmp演算法。kmp演算法 指標從模式串開頭移動,當匹配到z x時,尋找子串xzzx...

KMP演算法 字串匹配

kmp演算法基本思想 我們在用常規的思想做 字串匹配時候是 如 對如 字元如果 t abab 用p ba 去匹配,常規思路是 看 t 第乙個元素 a 是否 和p 的乙個 b 匹配 匹配的話 檢視各自的第二個元素,不匹配 則將 t 串的 第二個元素開始 和 p 的第乙個匹配,如此 一步一步 的後移 來...

KMP字串匹配演算法

kmp核心思想 計算模式串的next陣列,主串的索引在比較的過程中不回朔 ifndef kmp h define kmp h class kmp endif include kmp.h include include include using namespace std int kmp calcu...