KMP演算法--Next陣列詳解與優化
阿新 • • 發佈:2018-11-29
本篇文章直接跳過蠻力演算法以及一些簡單背景,著重討論Next陣列的意義以及其是如何工作的,並對如何求Next陣列做詳細記錄。
1.背景
1.1 KMP演算法的應用:KMP演算法用來解決模式串匹配問題。
1.2 為什麼要用KMP演算法:普通的蠻力演算法時間複雜度為O(n*m),而KMP為O(n+m)。
2.KMP演算法思想
2.1 KMP演算法的思想:(稱T為目標串,P為待查詢字串)
- 目標串T的 i 指標不必回溯!
- 通過對待查詢字串P進行分析得出當每次字元不匹配時P串應如何移動
2.2 Next陣列介紹:Next陣列中存的是如果P[ j ] != T[ i ] 時,j 應該等於多少。即P串要向右移動多少位,此時 i 不變。Next[ j]代表前 j - 1 個字元的最大字首和最大字尾相同的字元數
2.3 程式碼例項:
int KMP(string a,string b){ BuildNext(b);//用來求Next陣列 int n = a.length(); int m = b.length(); int i = 0,j = 0; while(j < m && i < n){ if(j < 0 || a[i] == b[j]) i++,j++; else j = Next[j]; } return i-j; }
3.Next陣列的求法
3.1 與KMP演算法本身類似的思想:將P串自己與自己匹配。
3.2 程式碼例項:
void BuildNext(string P){
int m = P.length();
int t = Next[0] = -1;
int j = 0;
while(j < m-1){
if(t < 0 || P[j] == P[t]){
j++;
t++;
Next[j] = t;//待優化
}else t = Next[t];
}
}
3.3 解析:這裡用到一個小技巧,令Next[0] = -1,叫做字元萬用字元
4.優化
4.1 對Next陣列進行優化:當待匹配子串中有較多相同字元時,以上方法還是會進行很多次無謂的比較,比如T : 00100010與P :00010進行匹配,當知道T中的第三位1與P中的第三位0不匹配時,上述方法會將P串向右移動一位,結果仍是不匹配,但是我們已經知道了0和1不等,這樣還有必要每次都向後移一位嗎?為什麼不直接移動3位?
4.2 實現方法:我們在求前 i - 1 個元素字首與字尾相等個數的時候,是不考慮第P[ i ]的,但是如果要是最大相等字首的後一個字元和P[ i ]相等的話,不就表明即使你移動了之後,當前字元依然沒變,也就依然不能匹配,所以要再接著移嗎?
4.3 程式碼例項:
void BuildNext(string P){
int m = P.length();
int t = Next[0] = -1;
int j = 0;
while(j < m-1){
if(t < 0 || P[j] == P[t]){
j++;
t++;
Next[j] = P[j] != P[t]?t:Next[t];
}else t = Next[t];
}
}
5.完整程式碼
#include<iostream>
#include<cstdio>
using namespace std;
const int maxn = 1e5;
int Next[maxn];
void BuildNext(string P){
int m = P.length();
int t = Next[0] = -1;
int j = 0;
while(j < m-1){
if(t < 0 || P[j] == P[t]){
j++;
t++;
Next[j] = P[j] != P[t]?t:Next[t];
}else t = Next[t];
}
}
int KMP(string a,string b){
BuildNext(b);
int n = a.length();
int m = b.length();
int i = 0,j = 0;
while(j < m && i < n){
if(j < 0 || a[i] == b[j])
i++,j++;
else j = Next[j];
}
return i-j;
}
int main()
{
string a,b;
getline(cin,a);
getline(cin,b);
int pos;
if(b.length() > a.length()) pos = -1;
else pos = KMP(a,b);
if(pos >= 0) printf("%d\n",pos);
else puts("NO");
}