KMP字串匹配演算法
寫在前面
KMP演算法
典型例題
輸入 第一行一個整數N,表示測試資料組數。 接下來的N*2行,每兩行表示一個測試資料。在每一個測試資料中,第一行為模式串,由不超過10^4 個大寫字母組成,第二行為原串,由不超過10^6 個大寫字母組成。 其中N<=20 輸出 對於每一個測試資料,按照它們在輸入中出現的順序輸出一行Ans,表示模式串在原串中出現的次數。
解法
暴力解:遍歷原串每一個元素逐一匹配模式串,這樣帶來的複雜度是o(n*m),n為原串長度,m為模式串長度,如果原串和模式串都很大的情況下這樣的複雜度是很難接受的。
逐一匹配的想法是簡單的,問題就在於這樣的暴力匹配,完全沒有用到前面匹配過程當中的任何資訊。分析這O(n*m)的複雜度來自哪裡就可以有一個清晰的認識了。 kmp演算法的核心就在於匹配過程當中模式串的回溯過程。暴力解當中對於模式串一旦不匹配就會回溯到模式串的頭部和下一個原串的元素進行匹配,對於原串來說上一次匹比較過的位置這一次還可能會被比較。對於kmp演算法而言,妙就妙在模式串的回溯不會一下子回溯到頭而是回溯到前面的某一個位置,同時原串根本不會回溯,也就是說原串的遍歷是一直向前的,如果某個位置發生了失配,模式串進行回溯直到模式串回溯到頭還沒匹配上,此時原串才往後遍歷下一個位置。對於原串的任意一個位置只被考慮和模式串進行一次匹配,而暴力解當中原串的任意一個位置的元素可能會被拿出來和模式串匹配m次。
kmp演算法的步驟分為2步:
- 根據模式串構造next陣列
- 使用next陣列對原串進行匹配
構建next陣列
對於next陣列的構建從理論上說: next[i] 等於模式串當中除去第i位之前的子串當中字首與字尾串當中匹配上的最大的子串的長度。這麼一說有點抽象,舉個例子來說吧:比如模式串為abcabdo 那麼對應的d位置的next[5] = 2,同理next[4] = 1,next[3] = 0,next[2] = 0,next[1] = 0; 但是對於next[0] 我們需要將其設定為-1。這是為了防止在第一個位置發生失配時一直迴圈。 從程式碼實現上來說: 雖然從理論上我們去逐個分析next陣列的值是很簡單的但是程式碼實現上卻有難度。但是也是可以發現,我們可以利用前面位置求出的next陣列的值作為這次的判斷基礎,不需要再從頭判斷,也就是說求出next陣列的過程是一個動態規劃填表的過程。next[i]表示的是當i位置出現失配的時候應該回溯到next[i]位置去進行再次匹配,那麼對於next[i]而言如果str_m[i-1]==str_m[next[i-1]] 也就是說對於i位置而言字首和字尾匹配的最大長度比i-1位置多1,此時next[i] = next[i-1] + 1。否則遞迴的往前去找next[next[i-1]]繼續匹配直到出現next為-1的情況為止。
kmp程式碼
#include <iostream>
#include <vector>
#include <string>
using namespace std;
vector<int> make_next(string& str_m) {
int n = str_m.size();
if(n==1) return vector<int> {-1};
vector<int> next(n,0);
next[0] = -1;
next[1] = 0;
int j = 0;
for(int i=2;i< n;++i) {
j = next[i-1];
while(j!=-1&&str_m[i-1]!=str_m[j])
j = next[j];
next[i] = j + 1;
}
return next;
}
int kmp_algorithm(string& str_m,string& str_p) {
int m = str_m.size(),n = str_p.size();
if(m>n||m==0||n==0) return 0;
if(m==n) return str_m==str_p;
int count = 0;
vector<int> next = make_next(str_m);
int j = 0;
for(int i=0;i<n;) {
if(j!=-1) {
if(str_m[j]==str_p[i]) {
if(j==m-1) {
++count;
j = next[j];
}
else {
++i;
++j;
}
}
else {
j = next[j];
}
}
else {
++i;
j = 0;
}
}
return count;
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
int N;
cin >> N;
vector<int> res;
for(int i=0;i<N;++i) {
string str_m,str_p;
cin >> str_m >> str_p;
res.push_back(kmp_algorithm(str_m,str_p));
}
for(auto each : res)
cout << each << endl;
return 0;
}