KMP 算法详解(CPP 实现)
轉載請標明出處:https://blog.csdn.net/kiss0tql/article/details/81416283
本文來自:deemo的博客
- 說明
- kmp 算法思想
- next 數組計算
- 字符串匹配
- 參考資料
說明
??kmp 算法是由 Knuth、Morris 和 Pratt 三人設計的一個非常高效的字符串匹配算法。該算法用于判斷一個字符串是否包含于另一個字符串中。該算法復雜度是 O(m+n)O(m+n),其中 mm 和 nn 分別是兩個字符串的長度。
kmp 算法思想
??我們能很容易想到 O(M?N)O(M?N) 復雜度的算法,即索引從字符串 str1 的第一個字符開始,依次檢查以該字符作為首字符的連續 N 個字符是否與字符串 str2 匹配,若不匹配,則索引向前移動一位,從第二個字符開始依次檢查,以此類推。
??該算法并沒有考慮到現已匹配的部分字符串的結構。已匹配的部分字符串中可能包含相同的部分,若利用這些相同的部分,我們可以一次性移動多位而不只是移動一位,這樣大大提高了算法的效率。
??假設現有兩個字符串 str1 和 str2,并且現已從 index1 處開始檢是否匹配。當我們檢測到 index2 處時,發現此時待檢測的兩個字符不相同(即圖中 A 和 B 部分字符不匹配,index2 和 index1 之間的字符均匹配),按照傳統思想我們將會從 index1 + 1 處的位置開始新一輪的檢測,這樣檢測的效率極低。當我們仔細觀察現已匹配的部分字符串時,若能找到字符串中首尾相同的部分(即圖中 1、2、3和4部分的字符串相同),則可以直接將 str2 向前移動 k 個位置(如圖所示),即圖中 2 和 4 部分將作為新匹配字符串的頭部,并接著從 str1 的 index2 位置開始檢測。若不含有相同的部分,此時不管是將 str2 向前移動多少位都不會出現匹配的字符串,因此,可以從 str1 的 index2 位置開始新的一輪檢測(即從 str2 的第一個字符開始匹配,可以理解為將 str2 向前移動了 str2 長度的位置)。
??現有如下兩個字符串:
const char *str1 = "bacbbacabadababacambabacadbacabacasdsd";const char *str2 = "bacabaca";??按照上述的匹配過程可以得到如下的匹配過程圖,當匹配到 index2 處時,發現字符 d 和 字符 c 不匹配,但已匹配的部分 bacaba 的首位相同的部分為 ba,該部分為字符串 bacaba 的最長的相同前綴和后綴,而在下一輪的匹配過程中,將 ba 部分作為新的首部,并接著 index2 處開始匹配。一次類推完成整個字符串的匹配過程。
??由以上匹配過程可知,每當開始新一輪的匹配時,我們需要知道當前已匹配的字符串中最長相同前綴和后綴的長度,以實現匹配過程中的快速跳轉。而當前已匹配的字符串長度可能為 1 ~ length(str2),因此,我們可以創建一個長度為 n 的數組 ( n = length(str2) ),用來保存不同長度字符串的最長相同前綴和后綴的長度。該數組為 next 數組。
next 數組計算
??next 數組中第 i 個數表示字符串索引從 0 到 i 的字符串最長相同前綴和后綴的長度。注意,前綴和后綴不包括字符串本身,比如 aaaa 相同的最長前綴和最長后綴是 aaa 。
??假設我們現在已經求得 next[0]、next[1]、…… next[ i-1 ],分別表示長度為 1 到 i 的字符串的最長相同前綴和后綴的長度。現在要求 next[ i ]。由下圖我們可以看到,如果位置 i 和位置 next[ i-1 ] 處的兩個字符相同(下標從零開始),則 next[ i ]等于 next[ i-1 ] + 1。
??如果兩個位置的字符不相同,我們可以將長度為 next[ i-1 ] 的字符串繼續分割,獲得其最大公共長度 next[ next[ i-1 ] -1 ],然后再和位置 i 的字符比較。由于長度為 next[ i-1 ] 的字符串的公共長度保存在索引為 next[ i-1 ] -1 的 next 數組中,而該字符串又包含相同的前綴和后綴,如下圖所示,如果位置 next[ next[ i-1 ] -1 ]和位置 i 的字符相同,則 next [i ]就等于 next[ next[ i -1 ] -1 ] + 1。如果不相等,就可以繼續分割長度為 next[ next[ i -1 ] -1 ] 的字符串,直到字符串長度為 0 為止。
??根據上述計算 next 數組的過程,可以寫出相應的求 next 數組的代碼(CPP實現):
vector<int> getNext(const char *str) {int len = strlen(str); // 字符串長度vector<int> next(len, 0); // 保存結果,next[0]=0for(int i = 1; i < len; i++){int k = next[i - 1]; // k 表示需要比較的位置,初始值為 next[i - 1]while(k > 0 && str[i] != str[k]) // 比較,若不相等則繼續分割,直到相等或為0(即不含相同部分)k = next[k - 1];if(str[i] == str[k]) // 若相等,則 next[i] = k + 1,否則為0,其中 k 為索引k++;next[i] = k; // 更新 next[i]}return next; }??在求解 next 數組后,可以利用該數組進行字符串匹配。
字符串匹配
??假若當前字符串 str1 的第 i 個字符正在與字符串 str2 的第 k 個字符進行匹配(i 和 k 均從 0 開始),若相等,則進行下一輪的匹配,即進行 str1 的 第 i+1 個字符與 str2 的第 k+1 個字符匹配;若不相等,則跟 str2 的第 next[k - 1] 字符進行匹配,直到 k = 0 為止。重復以上過程直到檢測完 str1 的所有字符。
??根據上述匹配過程,完成 CPP 代碼:
int search(const char *str1, const char *str2) {vector<int> next = getNext(str2); // 獲得 str2 的 next 數組int k = 0; // 記錄當前已匹配 str2 的索引int res = -1; // 保存匹配的字符串起始位置,若不存在,返回-1for(int i = 0; i < (int)strlen(str1); i++) // 第 i 輪匹配{while(k > 0 && str1[i] != str2[k]) // str1的第i個與str2的第k個字符進行比較,若不同,則k=next[k-1],直到k為0或相等為止k = next[k - 1];if(str1[i] == str2[k]) // 若相等,更新kk++;if(k == (int)strlen(str2)) // 若找到完全匹配{res = i - k + 1; // 保存匹配的字符串起始位置,此時根據需要可用容器保存多個結果k = next[k - 1]; // 進行下一輪匹配,此處根據需要可去掉}}return res; }??以上就是 KMP 算法的整個過程,完整的代碼可訪問 Github 下載。
參考資料
- KMP算法學習(詳解)
- KMP算法最淺顯理解 —— 一看就明白
以上內容已同步至 Github。
總結
以上是生活随笔為你收集整理的KMP 算法详解(CPP 实现)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 商务旅行代理服务市场现状研究分析-
- 下一篇: FineBI实现物流行业数据分析