首先是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数组无法满足,所以不采取优化版本