關於KMP演算法的筆記

2021-08-17 14:17:23 字數 4019 閱讀 1395

以前一直沒有搞懂kmp演算法,突然心血來潮,特意去網上搜尋資料下定決心弄懂,終於在一篇文章的幫助下,對kmp演算法有了自己的理解。

這篇文章的出處是:the knuth-morris-pratt algorithm in my own words

一、什麼是kmp演算法?

kmp演算法是一種改進的字串匹配演算法。kmp-wiki

假設現有乙個子串「abcd」,我們需要從乙個字串「ababcabcdabbcd」中找到該子串的出現位置,那麼最簡單粗暴的方法就是遍歷字串「ababcabcdabbcd」的每一位,在遍歷每一位的時候,逐個與子串的每一位進行比較,簡單來說就是兩個迴圈,內迴圈是從字串「ababcabcdabbcd」的某一位開始,不斷的與子串進行比較,外迴圈就是字串「ababcabcdabbcd」的遍歷。很顯然,這樣的比較的效率非常低下,為了提高匹配效率,才有了kmp演算法。

二、kmp演算法是如何運作的?

在說kmp演算法如何運作之前,需要介紹乙個kmp演算法的核心:區域性匹配表。(the partial match table)

char:  | a | b | a | b | a | b | c | a |

index: | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |

value: | 0 | 0 | 1 | 2 | 3 | 4 | 0 | 1 |

上面給出的就是乙個區域性匹配表。

第一行char代表的是乙個匹配子串,第二行是表的索引,第三行代表的是 該部分子串的真字首和真字尾的最長匹配長度。

第三行所代表的東西是不是有點難以理解,什麼叫"該部分子串的真字首和真字尾的最長匹配長度"的嘛?別急,乙個個來說。

1)什麼叫真字首、真字尾?

2)什麼叫真字首和真字尾的最長匹配長度?

3)區域性匹配表第三行所代表的意思

「該部分子串的真字首和真字尾的最長匹配長度",這個意思是

在index=0的時候,區域性匹配表的匹配子串是"a",因為子串"a"沒有真字首和後字首,所以"真字首和真字尾的最長匹配長度"為0;

在index=1的時候,子串為"ab",只有乙個真字首"a",也只有乙個真字尾為"b",因此"真字首和真字尾的最長匹配長度"也是0;

在index=2的時候,子串為"aba",真字首"a"、"ab",真字尾"a"、"ba",可以看到完全匹配的真字首和真字尾為"a",所以長度為1;

在index=5的時候,子串為"ababab",真字首有"a"、"ab"、"aba"、"abab"、"ababa",真字尾有"b"、"ab"、"bab"、"abab"、"babab",完全匹配的真字首和真字尾組合有:"ab"、"abab";明顯最長的是"abab",因此長度為4;

在index=6的時候,是沒有完全匹配的真字首和真字尾,這部分我就不列出來,你們可以自行驗證。因此長度為0。

三、區域性匹配表怎麼用?

kmp高效的原因就是在與它有效利用了已經進行比較過的字串資訊,有效地跳過一些重複比較。而它跳過演算法就在於區域性匹配表的使用。

"if a partial match of lengthpartial_match_lengthis found andtable[partial_match_length] > 1, we may skip aheadpartial_match_length - table[partial_match_length - 1]characters."

"用匹配字串去進行匹配的時候,如果匹配到長度(partial_match_length) 大於0的區域性匹配字串,且該區域性匹配字串對應的區域性匹配表(table)有table[partial_match_length-1]>0,則我們可以跳過partial_match_length-table[partial_match_length-1] 個字元。"

上面這句話就是kmp跳過字串的演算法,看著非常拗口,也許是我翻譯的問題,然後再結合例子陳述一下。

我們用匹配字串"abababca"去對字串"bacbababaabcbab":

首先得到匹配字串"abababca"的區域性匹配表:

char:  | a | b | a | b | a | b | c | a |

index: | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |

value: | 0 | 0 | 1 | 2 | 3 | 4 | 0 | 1 |

最先得到的區域性匹配是:

bacbababaabcbab

|abababca

區域性匹配是字串"a",長度為1,因為table[len-1](table[0])是0,所以不進行跳過字串操作。

bacbababaabcbab

|||||

abababca

再一次得到的區域性匹配如上圖所示,區域性匹配字串為"ababa",長度為5,而table[len-1](table[4])值為3,則跳過len-table[len-1](5-table[4] 或 5-3 或 2)個字元,即

// x denotes a skip

bacbababaabcbab

xx|||

abababca

接下來得到的區域性匹配是"aba",長度為3,table[len-1](table[2])的值為1,則跳過len-table[len-1](3-table[2] 或 3-1 或 2)個字元,即

// x denotes a skip

bacbababaabcbab

xx|abababca

到目前為止,剩餘需要進行比較的字串"abcbab"長度已經小於匹配字串的長度了,因此沒有必要繼續匹配了。

上面就是kmp演算法的執行過程了。

三、為什麼這麼kmp利用區域性匹配錶跳過字串的做法是正確的?

在這個問題下我只想從我個人理解出發談談,具體的數學證明建議搜尋資料。

首先,用乙個 匹配字串 去對乙個 字串 進行匹配的時候,肯定都是從 匹配字串的首位開始的,直到匹配完全才算是匹配成功。當匹配失敗的時候,會有兩種情況,一種沒有區域性匹配(就是第乙個字元都對不上),另外一種是有區域性匹配,現在就從有區域性匹配入手。

當存在區域性匹配的時候,為了高效地進行下一次匹配,最簡單的辦法就是將匹配字串向右平移(也就是跳過字串),但是具體要平移多少位呢?

在求跳過字串個數的時候,其實可以理解為採取的是一種貪心策略。從區域性匹配串的最右邊開始,用自身往左邊諸位增加匹配,這個操作很明顯就是求 區域性匹配串 的 真字首和真字尾的 最長匹配長度了。

可以用反證法來證明下這個操作的正確性。

首先原命題為:

貪心操作求出了最長匹配長度match_len,而區域性匹配字串總長度total_len。則跳過的最長位數為num=total_len-match_len。(即貪心操作一定求得出最長的跳過位數)

現在假設跳過的最長位數為num>total_len-match_len。(即貪心操作得到的不是最長的跳過位數)

如果num>total_len-match_len。而最長跳過位數是在保證沒有漏掉可以匹配的字串的情況下求出來的,如果說num>total_len-match_len,那麼就漏掉了從num-1位置開始匹配的這個情況,而這個情況是可以區域性匹配成功的。因此假設不成立。

關於KMP的筆記

今天資料結構課上講了kmp演算法,課上不是很理解,現在在紙上推算了一遍寫下來以供將來複習用。void getnext char s2,int next else intkmp char s1,char s2,int next else if j lens2 else int main char s1...

關於KMP演算法

複習的時候隨便寫寫的,用git太麻煩,就用csdn儲存下。kmp演算法寫起來很短,但是精髓是它的思想不太好理解,主串不用回溯,是因為模式串自己與自己比較匹配可以得出相應的next值,然後模式串向右滑動,例如,模式串abcd xxabci x在第i位失配,只需要模式串滑到d與主串繼續比較。哎呀表達得不...

關於KMP演算法

kmp演算法是非常經典的字串匹配演算法,而且有可能是最經典的乙個。同時它也是非常典型的一種優化演算法,它把原本暴力法o mn 的最壞複雜度降低到了o m n 雖然實際上暴力法的執行複雜度期望依然是線性的 其思想非常具有典型性和可借鑑性,值得好好學習。1 基本思想 kmp演算法的基本思想是,借助乙個預...