經典演算法 KMP,深入講解next陣列的求解

2021-08-03 22:20:30 字數 3936 閱讀 5025

原部落格位址 :

前言

之前對kmp演算法雖然了解它的原理,即求出p0···pi的最大相同前字尾長度k;但是問題在於如何求出這個最大前字尾長度呢?我覺得網上很多帖子都說的不是很清楚,

總感覺沒有把那層紙戳破,後來翻看演算法導論,32章 字串匹配雖然講到

了對前1.kmp演算法的原理:

本部分內容**:

字串匹配是計算機的基本任務之一。

舉例來說,有乙個字串"bbc abcdab abcdabcdabde",我想知道,裡面是否包含另乙個字串"abcdabd"?

許多演算法可以完成這個任務,knuth-morris-pratt演算法(簡稱kmp)是最常用的之一。它以三個發明者命名,起頭的那個k就是著名科學家donald knuth。

這種演算法不太容易理解,網上有很多解釋,但讀起來都很費勁。直到讀到jake boxer的文章,我才真正理解這種演算法。下面,我用自己的語言,試圖寫一篇比較好懂的kmp演算法解釋。

2.首先,字串"bbc abcdab abcdabcdabde"的第乙個字元與搜尋詞"abcdabd"的第乙個字元,進行比較。因為b與a不匹配,所以搜尋詞後移一位。

3.因為b與a不匹配,搜尋詞再往後移。

4.就這樣,直到字串有乙個字元,與搜尋詞的第乙個字元相同為止。

5.接著比較字串和搜尋詞的下乙個字元,還是相同。

6.直到字串有乙個字元,與搜尋詞對應的字元不相同為止。

7.這時,最自然的反應是,將搜尋詞整個後移一位,再從頭逐個比較。這樣做雖然可行,但是效率很差,因為你要把"搜尋位置"移到已經比較過的位置,重比一遍。

9.怎麼做到這一點呢?可以針對搜尋詞,算出一張《部分匹配表》(partial match table)。這張表是如何產生的,後面再介紹,這裡只要會用就可以了。

已知空格與d不匹配時,前面六個字元"abcdab"是匹配的。查表可知,最後乙個匹配字元b對應的"部分匹配值"為2,因此按照下面的公式算出向後移動的位數:

移動位數 = 已匹配的字元數 - 對應的部分匹配值

10.因為 6 - 2 等於4,所以將搜尋詞向後移動4位。

12. 因為空格與a不匹配,繼續後移一位。

13.逐位比較,直到發現c與d不匹配。於是,移動位數 = 6 - 2,繼續將搜尋詞向後移動4位。

15.     下面介紹《部分匹配表》是如何產生的。

首先,要了解兩個概念:"字首"和"字尾"。 "字首"指除了最後乙個字元以外,乙個字串的全部頭部組合;"字尾"指除了第乙個字元以外,乙個字串的全部尾部組合。

"部分匹配值"就是"字首"和"字尾"的最長的共有元素的長度。以"abcdabd"為例,

- "a"的字首和字尾都為空集,共有元素的長度為0;

- "ab"的字首為[a],字尾為[b],共有元素的長度為0;

- "abc"的字首為[a, ab],字尾為[bc, c],共有元素的長度0;

- "abcd"的字首為[a, ab, abc],字尾為[bcd, cd, d],共有元素的長度為0;

- "abcda"的字首為[a, ab, abc, abcd],字尾為[bcda, cda, da, a],共有元素為"a",長度為1;

- "abcdab"的字首為[a, ab, abc, abcd, abcda],字尾為[bcdab, cdab, dab, ab, b],共有元素為"ab",長度為2;

- "abcdabd"的字首為[a, ab, abc, abcd, abcda, abcdab],字尾為[bcdabd, cdabd, dabd, abd, bd, d],共有元素的長度為0。

16.

"部分匹配"的實質是,有時候,字串頭部和尾部會有重複。比如,"abcdab"之中有兩個"ab",那麼它的"部分匹配值"就是2("ab"的長度)。搜尋詞移動的時候,第乙個"ab"向後移動4位(字串長度-部分匹配值),就可以來到第二個"ab"的位置。

2.next陣列的求解思路

通過上文完全可以對kmp演算法的原理有個清晰的了解,那麼下一步就是程式設計實現了,其中最重要的就是如何根據待匹配的模版字串求出對應每一位的最大相同前字尾的長度。我先給出我的**:

void makenext(const char p,int next)

next[q] = k;}}

現在我著重講解一下while迴圈所做的工作:

已知前一步計算時最大相同的前字尾長度為k(k>0),即p[0]···p[k-1];

此時比較第k項p[k]與p[q],如圖1所示

如果p[k]等於p[q],那麼很簡單跳出while迴圈;

關鍵!關鍵有木有!關鍵如果不等呢???那麼我們應該利用已經得到的next[0]···next[k-1]來求p[0]···p[k-1]這個子串中最大相同前字尾,可能有同學要問了——為什麼要求p[0]···p[k-1]的最大相同前字尾呢???是啊!為什麼呢?原因在於p[k]已經和p[q]失配了,而且p[q-k] ··· p[q-1]又與p[0] ···p[k-1]相同,看來p[0]···p[k-1]這麼長的子串是用不了了,那麼我要找個同樣也是p[0]打頭、p[k-1]結尾的子串即p[0]···p[j-1](j==next[k-1]),看看它的下一項p[j]是否能和p[q]匹配。如圖2所示

附**:

#include#includevoid makenext(const char p,int next)

next[q] = k;

}}int kmp(const char t,const char p,int next)

if (q == m)

}

}int main()

; char t = "ababxbababcadfdsss";

char p = "abcdabd";

printf("%s\n",t);

printf("%s\n",p );

// makenext(p,next);

kmp(t,p,next);

for (i = 0; i < strlen(p); ++i)

printf("\n");

return 0;

}

經典演算法 KMP,深入講解next陣列的求解

本部分內容 字串匹配是計算機的基本任務之一。舉例來說,有乙個字串 bbc abcdab abcdabcdabde 我想知道,裡面是否包含另乙個字串 abcdabd 許多演算法可以完成這個任務,knuth morris pratt演算法 簡稱kmp 是最常用的之一。它以三個發明者命名,起頭的那個k就是...

kmp演算法next例題 KMP演算法next陣列求解

kmp演算法與bf演算法的比較 bf演算法的想法十分樸素,即先將子串t的第一位與主串s的第一位對齊開始匹配,當不能匹配時將子串整體往後移一位,然後重新匹配,以此類推直至排出結果 如當遇到下圖所示情況時,需將子串整體後移一位,將i,j分別回溯到主串第2位和子串第一位。kmp演算法 對bf進行思考後,我...

KMP演算法經典講解

kmp演算法的思想都是一樣的,看了很多講解,基本上都只是在next陣列的表示上是有區別的,我覺得乙個很6的大神,講的很好 這位大神的next i 表示的是如果模板串p的第i位與母串s匹配失敗,則從模板串的哪個位置開始進行下一次比較。母串s和模板串p都是從0開始到len 1,next陣列也是從0開始,...