1. 程式人生 > >KMP演算法--Next陣列詳解與優化

KMP演算法--Next陣列詳解與優化

本篇文章直接跳過蠻力演算法以及一些簡單背景,著重討論Next陣列的意義以及其是如何工作的,並對如何求Next陣列做詳細記錄。

1.背景

1.1 KMP演算法的應用:KMP演算法用來解決模式串匹配問題。

1.2 為什麼要用KMP演算法:普通的蠻力演算法時間複雜度為O(n*m),而KMP為O(n+m)。

2.KMP演算法思想

2.1 KMP演算法的思想:(稱T為目標串,P為待查詢字串)

  1. 目標串T的 i 指標不必回溯!
  2. 通過對待查詢字串P進行分析得出當每次字元不匹配時P串應如何移動

2.2 Next陣列介紹:Next陣列中存的是如果P[ j ] != T[ i ] 時,j 應該等於多少。即P串要向右移動多少位,此時 i 不變。Next[ j]代表前 j - 1 個字元的最大字首和最大字尾相同的字元數

,這樣當P[ j ] != T[ i ]時,將j = Next[ j ] ,由於字首與字尾相等,故此時前 j 個字元仍是匹配的。

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,叫做字元萬用字元

,即可以和所有字元匹配,這樣就可以在P沒有字首和T[i]匹配時P從頭開始比較。

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");
}