完全搞懂KMP演算法

2021-07-05 10:52:19 字數 4552 閱讀 2092

在串的模式匹配演算法中,kmp演算法稱得上是經典演算法了。簡約而不簡單,是對kmp演算法最恰當的評價。該演算法是由d.e.knuth,j.h.morris和v.r.pratt三人同時發現,因此人們稱它為kmp演算法。廢話不多說,現在開始進入正題。

首先宣告兩點:

字串 採用定長順序儲存結構,主串用s表示,模式串用t來表示。用n來表示主串s的有效字元長度,用m來表示子串t中有效字元的長度。

陣列下標從0開始,但要注意,陣列s[0]和t[0] 中存放的是串的長度。也就是說,字串從s[1]和t[1]開始。(與嚴蔚敏老師的書保持一致)

主串s:     ababcababa 用 i 指示主串中正待比較的字元

模式串t:  ababa          用 j 指示子串中正待比較的字元 

目標:對於給定的主串s=『

ababcababa

』 和模式串t='

ababa

',判斷串t 是否在 串s中,如果在,返回起始位置。

想要實現該目標,有很多方法,本文只介紹兩種,即bf演算法和kmp演算法。

首先,讓我們完全從直覺出發,來認識一下最簡單的字串模式匹配演算法——bf演算法(又叫樸素的串匹配演算法)。

bf演算法是最笨的演算法,也是一看就懂的演算法。該演算法在有些情況下時間複雜度為o(n+m),但是,在最壞的情況下,時間複雜度會飆公升到o(n*m)。但是,bf演算法也是理解kmp演算法的基礎。該演算法的主要思想是:從主串s的第乙個字元起,和模式串t中的第乙個字元比較,若相等,則繼續逐個比較後續的每個字元;否則就從主串的下乙個字元起再重新和模式串t比較。

再次強調一遍,i 指向主串,j 指向子串。

演算法的c語言**表示如下:

int index(const char* s, const char* t)

else

} if(j > t[0])

return i - t[0];

else

return -1;

}

下圖所示的為bf演算法的一種最壞的情況,其中主串s為'aaaaaaaaaaaaaaaaaab',子串t為'aaaaab'。

我們可以看到,當乙個乙個字元比較,遇到不相同的字元時,bf演算法的做法是讓子串向右移動一位,然後重新子串頭部開始比較。

為了更清楚地看到bf演算法的缺陷,我們再來看乙個bf演算法的例子。

從該例子中可以看出,每次遇到不等的字元時,主串的「指標」i就要回退,而且子串每次也只是向右移動一位,這兩點導致bf演算法效率低至o(n*m),令人髮指。

能不能換種方法,使得每次遇到不匹配的字元時,i呆在原地,不回退呢?同時,能不能想辦法讓子串每次向右移動的時候,在保證正確的情況下,子串盡量多 右移幾位呢?

答案是 可以(both)。這也正是kmp的做法。

我們在介紹kmp演算法之前,先回頭再研究一下bf演算法,看一下該演算法中遺漏掉了什麼有用的資訊。

如下圖所示,當匹配到字元a處時,顯然字元a與字元b不同,即串t匹配主串t失敗。這時,子串t要一位一位右移,每移動一位,都要重新從頭開始匹配。好,經歷千辛萬苦,終於,當下一次重新匹配到字元a處時(即b與c段完全相同),這時,先別著急著判斷問號處的字元和字元a是否相同。

我們來觀察一下這個狀態有什麼特殊之處。因為已經匹配到a處,所以之前的內容是完全相同的,即b與c段相同。之前,已經有c與a段相同了。哈哈,我們發現了,原來當右移重新匹配到上一次失敗點的時候,必有a與b段相同。

b段顯然是串t的乙個字首,而a段是失敗點之前的串的乙個字尾。為了不遺失可能正確的匹配結果,我們要再a與b第一次相同時就停止右移,即要保證a與b相同時,b的長度最大。

b是子串t的乙個字首,a是截止到b為止的子串t的乙個字尾,且a與b完全相同。

用官方的術語來說,就是尋找匹配失敗點之前的 subt的最長的字首和字尾相等的串。

好了,上面講了那麼多,就是為了這一句話。只有理解了上面這一句話,你才會心悅誠服地 理解並記憶kmp。

kmp演算法的整個流程就是: 

1. 將s串和t串從第乙個字元開始匹配; 

2. 如果匹配成功,則subt即灰色部分增加;

3. 如果不成功,則t向後滑動,使滑動後的subt的字首和滑動前的subt的字尾重合,再對問號處 進行匹配,如果還不成功,則再次滑動t,直到匹配成功或者t滑動到a處。

4. 如果到了a處,則從t串的起始位置進行匹配,跳至步驟1。 

從上面的步驟可以知道,匹配失敗後,每次滑動到**,只與子串t本身有關,而與主串s沒有任何關係。所以,我們可以在匹配主串之前,先對子串t進行預處理,得到每個失敗點 應該向前滑行的位置,用乙個next陣列來儲存。這樣,以後每次匹配失敗,只用o(1)的時間查詢next陣列,直接將j指向那裡,再重新開始與主串失敗點處進行比較就ok了。

好了,理解了演算法的精髓,就一馬平川了。

我們假定,當比較到子串的第j個字元時,遇到了失敗點。

此時:我們假設此時主串s中的失敗點應該與子串t中的第k個字元繼續比較,顯然,k

有了next陣列,我們先假定next陣列已經求好了,那麼kmp演算法就是小菜一碟了。

int kmp(const char* s, const char* t)

//繼續比較後繼字元

else j = next[j]; //匹配失敗,模式串向右移動 }

if( j > t[0])

return i - t[0]; //匹配成功

else return 0;

}

可見,next陣列的求法 才是kmp演算法的關鍵,也是我們要講的大頭。下面,讓我們開始進入next陣列的求解環節吧。勝利就是眼前。

子串中第1個字元到第j-1個字元都匹配成功,第j個字元匹配失敗,則要查詢next[j]。

記next[j] = k,則k表示子串中第j個字元與主串匹配失敗之後,應該將主串中的失敗點與子串中的第k個字元相比較。

也就是說,在子串第j個字元之前,存在如下關係:

並且k是滿足上述關係式的最大值。(k < j)

好的。下面我們用類似數學歸納法的方法,也就是遞推(不是遞迴)來求next陣列。

當next[j] = k時,我們來求next[j+1]。

存在兩種情況:

第一種情況很簡單,不多說了。

第二種情況,表明k也是失敗點,則我們仍要繼續取next[k],然後比較next[k]處的字元是否與pj相等,如果相等,則next[j+1] = next[k] + 1,如果還是不等,則繼續向前迭代。直至到子串的開頭為止,即j==0為止。用c語言表述next陣列的求法如下所示:

void get_next(const char*t, int next)

else j = next[j];

}}

我們可以看出,該函式的時間複雜度為o(m),m為子串t的長度。

不過,用該方法求得的next陣列仍然有缺陷。我們舉個例子就一目了然了。

主串s: 'aaabaaaab'

模式串t: 『aaaab』

根據t可以得出next陣列為j1

2345

模式串aaa

abnext[j]01

234 再次強調一下,陣列的第乙個元素t[0]存放的是陣列t的長度。真正的元素從t[1]開始儲存。

我們看到,當i=4, j=4時,子串中t[4]a與主串的字元不匹配,則還要由next[j]的指示依次進行j=3, j=2, j=1這3次比較。但是,由於這四個字元也都相同,因此,假如j=4與主串i=4不匹配的話,就可以將子串一口氣向右滑動4個字元,直接進行i=5, j=1的比較。

即我們再給next陣列 遞推賦值的時候,next[i] = j,還要再判斷一下 t[i] 與 t[j] 是否相等。

因為正是由於子串t[i]與主串相比不符,失敗的情況下才查詢next[i],如果此時next[i]仍然是同一字元,那麼比較肯定還是會失敗的,所以如果t[i] == t[j],則next[i] = next[j].

修正後的next陣列求法c語言描述如下所示:

void get_next(const char*t, int next)

else j = next[j];

}}

修改之後的next陣列如下: j1

2345

模式串taa

aabnext[j]01

234修改後next[j]00

004

至此為止,kmp演算法全部搞定。

演算法不容易理解,謹記之。

完全掌握KMP演算法思想

學過資料結構的人,都對kmp演算法印象頗深。尤其是新手,更是難以理解其涵義,搞得一頭霧水。今天我們就來面對它,不將它徹底搞懂,誓不罷休。如今,大夥基本上都用嚴蔚敏老師的書,那我就以此來講解kmp演算法。小弟正在備戰考研,為了節省時間,很多課本上的話我都在此省略了,以後一定補上。嚴老的 資料結構 79...

演算法 KMP演算法完全解析(C語言實現)

kmp演算法所做的事情,就是在字串中尋找子串。比如ilovecode這個字串中,我們可以搜尋到love這個子串。但如果用回溯的暴力方法尋找子串 即兩個for迴圈 雖然思路簡單,但是時間複雜度為o m n 借助kmp演算法,可將複雜度降為乙個迴圈,增進了效率。1 字首和字尾的概念 字首 指的是從首字元...

誰能完全搞懂Visual Studio的安裝項?

大家都知道,visual studio絕對不是 乙個程式 那麼簡單,不管哪個版本,安裝好之後總會在 刪除程式 中生成一大堆你或懂或不懂的東西。但很少人關注過究竟包括哪些東西。我最近裝了一次visual studio 2012 ultimate 安裝時僅選擇了一項 microsoft web deve...