首先是KMP算法的標準模板
#include <iostream>
#include <cstring>
using namespace std;
int* buildNext(char* P){
int m = strlen(P);
int j = 0;
int *N = new int[m]; // N[j]表示對於模式串的長度爲j的前綴,能錯位匹配的最大長度
// N[j]也可以解釋爲:如果第j個字符失配了,那麼應該從N[j]開始繼續嘗試
int t = N[0] = -1; // t是當前能錯位匹配的最大長度
while(j < m-1){
if(t < 0 || P[j] == P[t]) // 如果下一位也能匹配
N[++j] = ++t; // 記錄,j移向下一位,t增大1
else // 下一位不能匹配
t = N[t]; // 窗口向右滑動
}
return N;
}
int kmp(char* T, char* P){
int n = strlen(T);
int m = strlen(P);
int* N = buildNext(P);
int i = 0, j = 0;
while(i < n && j < m){
if(j < 0 || T[i] == P[j])
++i, ++j;
else
j = N[j];
}
}
int main()
{
char s1[1000005], s2[1000005];
cin >> s1 >> s2;
cout << kmp(s1, s2) << endl;
system("pause");
return 0;
}
經過改進的KMP算法如下,改動在第9行。
int* buildNext(char* P){
int m = strlen(P);
int j = 0;
int *N = new int[m]; // N[j]表示對於模式串的長度爲j的前綴,能錯位匹配的最大長度
// N[j]也可以解釋爲:如果第j個字符失配了,那麼應該從N[j]開始繼續嘗試
int t = N[0] = -1; // t是當前能錯位匹配的最大長度
while(j < m-1){
if(t < 0 || P[j] == P[t]) // 如果下一位也能匹配
++j, ++t, N[j] = (P[j] != P[t] ? t : N[t]); // [little change here]
// 這樣理解:N[j]也可以解釋爲:如果第j個字符失配了,那麼應該從N[j]開始繼續嘗試
// 如果P[N[j]]與P[j]相同,一定會繼續失敗,與其等到時候再滑動窗口,不如現在就滑動,所以修改爲N[j] = N[N[j]]
// 根據遞歸的方法,如果0~j-1的N[]都弄成了最優的,那麼當前也會是最優的
else // 下一位不能匹配
t = N[t]; // 窗口向右滑動
}
return N;
}
最後來一道KMP模板題。與上述代碼不同的地方在於,本題要求輸出所有的匹配位置,因此當j == m時不退出循環,而是繼續滑動窗口(j = N[j]),當然,next數組的長度也要相應地從m變爲m+1
#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;
int* buildNext(char* P){
int m = strlen(P);
int* N = new int[m+1];
int j = 0;
int t = N[0] = -1;
while(j < m){
if(t < 0 || P[t] == P[j])
N[++j] = ++t;
else
t = N[t];
}
return N;
}
int main()
{
char T[1000005], P[1000005];
scanf("%s%s", T, P);
int n = strlen(T);
int m = strlen(P);
int i = 0, j = 0;
int* N = buildNext(P);
while(i < n){
if(j < 0 || T[i] == P[j])
++i, ++j;
else
j = N[j];
if(j == m){
printf("%d\n", i-j+1);
j = N[j]; // 常規滑動窗口
}
}
for(j = 1; j <= m; ++j)
printf("%d ", N[j]);
printf("\n");
system("pause");
return 0;
}
由於這道題的特殊要求,改進後的next數組無法滿足,所以不採取優化版本