KMP算法最大的难点的难点在于如何处理P数组。
P[i]表示长度为i的字符串中最长且相等的的前缀和后缀,比如abbab中开头的ab和末尾的ab相等,那么p[5]=2。
先给出一段预处理的代码,从代码来分析做法:
void prework(char s[]) // s数组从下标1开始存储字符
{
int len = strlen(s+1);
next[1] = 0;
int j = 0;
for (int i = 1; i < len; i++)
{
//末尾新的字符和上一轮中最长前缀的后一个字符不匹配,j向前移动到相同字符处
while (j > 0 && s[j + 1] != s[i + 1])
j = next[j];
//后退操作后的前缀和末尾字符进行比较
if (s[j + 1] == s[i + 1])
j++;
next[i + 1] = j;
}
}
其中while循环是比较难理解的一个地方。
以abaababa为例,当取出abaabab这个字串进行判断时,j=3,i=6,此时a[j + 1] != a[i + 1]是成立的,需要进行回退,即进行j = p[j]的操作,回退后j=1,a[j + 1] == a[i + 1]成立,退出循环。
这一步的操作是使j回退到上一个相同字符处,因为a[ j ]=a[ p[ j ] ],此时再进行判断后一位字符是否相等,如果还不相等就继续回退,直到出现相等的字符或者j=0(没有符合情况的字符)。
KMP代码:
void KMP(char s1[], char s2[]) // s1和s2从下标1开始存储字符
{
int len1 = strlen(s1 + 1);
int len2 = strlen(s2 + 1);
int j = 0;
for (int i = 1; i <= len1; i++)
{
while (j && s2[j + 1] != s1[i])
j = next[j];
if (s2[j + 1] == s1[i]) j++;
if (j == len2)
{
//如果找到了相同的串,输出s1中起始位置
std::cout << i - len2 + 1 << std::endl;
j = next[j];
}
}
}
#include <bits/stdc++.h>
#define maxn 1000005
using namespace std;
int p[maxn];
char s1[maxn], s2[maxn];
void prework(char *a, int m) //a数组从下标1开始存储字符
{
p[1] = 0;
int j = 0;
for (int i = 1; i < m; i++)
{
//末尾新的字符和上一轮中最长前缀的后一个字符不匹配,后退一步
while (j > 0 && a[j + 1] != a[i + 1])
j = p[j];
//后退操作后的前缀和末尾字符进行比较
if (a[j + 1] == a[i + 1])
j++;
p[i + 1] = j;
}
}
int main()
{
scanf("%s %s", s1 + 1, s2 + 1);
int len1 = strlen(s1 + 1), len2 = strlen(s2 + 1);
prework(s2, len2);
int i = 0, j = 0;
//进行匹配
for (int i = 1; i <= len1;i++)
{
//出现不匹配的地方将j回退
//如果j=0就退出循环
while(j && s2[j+1]!=s1[i])
j = p[j];
if(s1[i]==s2[j+1])
j++;
if(j==len2)
printf("%d\n", i - len2 + 1), j = p[j];
}
for (int i = 1; i <= len2; i++)
printf("%d ", p[i]);
return 0;
}