關於KMP算法(c++實現)


簡介

KMP算法主要用於查找字符串,是 D.E.Knuth、J,H,Morris 和 V.R.Pratt 三位神人共同提出的,稱之為 Knuth-Morria-Pratt 算法,簡稱 KMP 算法。該算法相對於 Brute-Force(暴力)算法有比較大的改進,主要是消除了主串指針的回溯,從而使算法效率有了某種程度的提高。

暴力算法

講KMP算法之前,我們要先講暴力算法

算法原理

暴力算法就是在一段文字中一個一個的匹配字符串,匹配不成功從頭開始匹配
例如,從下圖文字中找到"ABCABCD"
在這里插入圖片描述
在這里插入圖片描述
 

第一次匹配

在這里插入圖片描述
匹配第七個字符’D’時,沒匹配到,繼續從第二個位置重新開始匹配

第二次匹配

在這里插入圖片描述
匹配第一個字符’A’時,沒匹配到,繼續從第三個位置重新開始匹配

第三次匹配

在這里插入圖片描述
匹配第一個字符’A’時,沒匹配到,繼續從第四個位置重新開始匹配

第四次匹配

在這里插入圖片描述
匹配第七個字符’D’時,沒匹配到,繼續從第五個位置重新開始匹配
總共要匹配17次

C++語言實例

int ViolentMatch(string text, string pattern) //text是文本串,pattern是關鍵字 { int tLen = text.size(); int pLen = pattern.size(); int i = 0; int j = 0; while (i < tLen && j < pLen) { if (text[i] == pattern[j]) { //如果當前字符匹配成功(即text[i] == pattern[j]),匹配下一個字符 i++; j++; } else //如果當前字符匹配失敗,pattern從頭開始匹配 { i = i - j + 1; j = 0; } } if (j == pLen) //當pattern全部匹配成功,返回匹配位置 return i - j; else return -1; } 

總結

每次匹配字符串失敗后都要回從頭開始匹配,如果文本串的長度是n,關鍵字的長度是m,那么算法效率(最差)就是n*m

以上內容來自:3月24日白羊座的csdn博客,感謝!

為什么要使用KMP算法?

解讀KMP之前,我們先來理解一下KMP算法存在的理由。對於模式匹配,目前所學的最簡單的是BF算法,即偏向於“暴力”匹配的方法。另外一種就是較為復雜KMP算法了。而倆者的區別在於:BF算法是時間復雜度相對高的,KMP則可以理解為用空間換時間。
暴力算法:逐個匹配主串字符,然后模式串j值回溯到1重新匹配。
KMP算法: KMP只需要將j值模式串中j的位置回溯到next[j]位,而免除了前面不需要的匹配,以此來換取時間。
相比一個一個比較,而KMP則在此基礎上更加的簡便了。
接下來,我們來用有邏輯的語言來了解KMP算法。而后,再解釋如何用代碼實現該算法。
首先,我們從代碼的實現效果來看,主串與模式串每次歷往KMP算法,模式串中移動至K位與失配的(主串)的i位對其,而不需要像BF算法一樣,一次次回溯從頭開始。

 

KMP算法的實現步驟

OK,我們現在什么廢話都不講。先前的直播中我們多次提到了next數組。我們都不管他長什么樣存的是什么東西了,直接上實踐例子。
給你文本:
"BBCABCDABABCDABCDABDE"
待匹配文本:
"ABCDABD"
和一個next數組
next[] = {0,0,1,1,2,0,1,2,3,4,5} 
再給你一個公式:
移動位數 =  已匹配值   -    部分匹配值 (其實就是直接從最長前綴直接跳到與他相等的最長后綴那里開始往后匹配)
(說人話:移動位數 =  已匹配值   -    最大公共元素長度)
現在我一步一步模擬一下KMP匹配的過程

1.第一步匹配與暴力沒有區別,找到第一個都是A的點開始匹配:

 

 

 

可以看到,我們匹配到第6位的時候,匹配失敗了,我們直接中斷本次匹配
然后移動 已匹配值   -    最大公共元素長度 位
我們匹配了5位,對應的next[5] = 2,那么我們就應該移動 5 - 2 = 3位。
結果如下:

很好,我們現在又可以進行匹配了,匹配到第11位時,又匹配失敗了,我們同樣中斷本次匹配並進行移動操作。
如圖:

 

 

繼續套公式,我們需要移動已匹配值   -    最大公共元素長度 位
我們匹配了10位,對應的next[10] = 4,那么我們移動10-4 = 6位,結果如下:

 

 


如法炮制,我們繼續進行匹配,匹配到第5位的時候又出錯了,繼續進行處理:

 

 


移動已匹配值   -    最大公共元素長度 位
我們匹配了4位,對應next[4] = 1,我們需要移動4 - 1 = 3 位。
結果:

 

 


這次拉跨了,只匹配到了1位...... 但我們依然要繼續操作~

 

 


移動已匹配值   -    最大公共元素長度 位
這次匹配了1位,對應next[1] = 0,我們要移動 1 - 0 = 1 位

 

 

 

 


很好!我們這次可算是匹配上了,至此,一次KMP字符串匹配操作就完成了,跑一下程序,我們是在文本的第

下標處完成的匹配,自己驗證一下看是否正確吧

(當然是正確的啦!不過還是提一下:next數組我們是從1開始計數,text文本是從0開始計數)

對應上面這個例子,有動圖可以看,不過我覺得不是很清晰,還是自己手做了模擬
在這里插入圖片描述

說明一下:

現在知道next數組的作業和kmp算法的查找過程了吧。那么很明顯,在kmp算法中最重要的部分就是
next[] 數組。它存放的是我們當前位置下的最大公共元素長度
(說人話:我也不知道該怎么說了,直接給大家模擬一下這個數組內的值是怎么來的吧)
 

我之前有說過:前綴,后綴,其實非常簡單的兩個概念。
我們還是拿一個待匹配文本
"ABCDABD"來說事

我直接上圖,大家就可以看懂咯~

看懂了嗎?前綴后綴就是:把字符串的子串分解出來,從前到后(前綴),從后到前(后綴)的一個排列
!注意:(前綴得不最后一位,后綴得不到最前一位,看圖理解!)
然后我們找出前綴和后綴中相同的子排列,將它們的長度保存到字符串子串的長度下的下標中去(就是第一列字符串的長度做下標)
這也可以很淺顯的讓大家理解,為什么next數組計數是從1開始的了吧~
另外,大家應該發現了next數組是對待匹配文本的處理吧
所以next數組的長度應該 = 待匹配文本的長度
只要我們求出next數組,就可以愉快的進行KMP字符串匹配咯

來,檢驗一下你能否自己獨立模擬出來整個KMP的過程~
給你文本:"BBCABCDABABCDABCDABDE"
待匹配文本:"ABCDABD"
還有上圖已經給出了的next數組:next[] = {0,0,0,0,1,2,0}
請你現在手動畫圖模擬一下過程,然后再來跟我的程序對一下答案(如果找到了,請輸出匹配處的下標,否則請輸出-1)
 

不要偷偷看哦~

 

 

 

 

 

 

 

 

蕪湖!答案還是13~,模擬對了嗎?

最后:代碼時間

KMP算法由兩部分組成
首先是對待匹配文本的處理——生成next數組
其次才是進行kmp匹配的模擬。
c++代碼如下:
 

#include <bits/stdc++.h> using namespace std; //在關鍵字中記錄與前綴匹配的組合,記錄在next數組 void make_next(string pattern, int *next) { int q, k; //q是匹配的位置,k是前綴開始的地方 int m = pattern.size(); //關鍵字的長度 next[0] = 0; //用於記錄關鍵字中與前綴匹配的組合的位置 for (q = 1, k = 0; q < m; q++) { //q是后面的數,k是前綴開始的地方 while (k > 0 && pattern[q] != pattern[k]) { //當后面的組合與前綴不同時,前綴數-1繼續匹配 k = next[k - 1]; } if (pattern[q] == pattern[k]) { // 如果前綴與后面有相同字符 k++; } next[q] = k; //用於記錄關鍵字中與前綴匹配的組合的位置 } } //kmp算法 int kmp(string text, string pattern, int *next) { int n = text.size(); //文本串的長度 int m = pattern.size(); //關鍵字的長度 make_next(pattern, next); //在關鍵字中記錄與前綴匹配的組合,記錄在next數組 int i, q; for (i = 0, q = 0; i < n; i++) { //i --> text, q --> pattern while (q > 0 && pattern[q] != text[i]) { //如果匹配失敗,往前找與前綴相同組合的位置 q = next[q - 1]; //只要next[q-1]不大於0,那么就是還沒找到與前綴相同的組合 } //繼續在循環中找到與前綴相同的組合,直到next[q-1]等於0 if (pattern[q] == text[i]) { //如果匹配正確,關鍵字位置+1 q++; } if (q == m) { //如果q的位置是關鍵字的長度,那么就是找到字符串了 return i - q + 1; //返回字符串的位置 } } return -1; //整個文本串找完都沒有找到字符串,返回-1 } int main() { string text = "ABAABAABBABAAABAABBABAAB"; string temp = "ABAABBABAAB"; int len = temp.size(); int arr[len+1]; make_next(temp, arr); cout << kmp(text, temp, arr); return 0; }

我把一些細節都寫在了注釋內,請大家細品。
(另外我也手寫不出來,查找了一下資料才寫出來的哈哈。)

最后感謝這兩篇博客對我的啟發,讓我能寫下這篇博客~
長臂人猿- 通俗易懂的KMP算法詳解(嚴蔚敏版C語言)
3月24日白羊座 - KMP算法(看一遍解決)

挺晚的了,晚安~


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM