KMP算法
前缀:只包含首字母,不包含尾字母的所有子串
后缀:只包含尾字母,不包含首字母的所有子串
例: 文本串:aabaabaaf
模式串:aabaaf
模式串的所有前缀:a,aa,aab,aaba,aabaa
模式串的所有后缀:f,af,aaf,baaf,abaaf
求最长相等前后缀
a 0
aa 1
aab 0
aaba 1
aabaa 2
aabaaf 0
当后缀的后面与文本串不匹配时,就从前缀的后面开始匹配
最长相等前后缀的大小正好等于前缀后面的下标
字符串 | a | a | b | a | a | f |
---|---|---|---|---|---|---|
前缀表 | 0 | 1 | 0 | 1 | 2 | 0 |
index | 0 | 1 | 2 | 3 | 4 | 5 |
定义两个变量i,j分别表示后缀末尾和前缀末尾 //前缀的末尾是size-1,后缀的末尾是1
当前缀末尾和后缀末尾不匹配的时候,j向前回退,前一位的next数组的值就是j要回退的下标
即当字符串匹配到f后发现失败,就会从下标为2的字符开始重新匹配即从b开始匹配
遇见冲突看前一位
数组f的求法一样
void getNext(int next[],char b[])
{
next[0] = 0;
int i, j = 0;//j指向前缀末尾位置和最长相等前后缀的长度,i指向后缀末尾位置
for (int i = 1; i < strlen(b); ++i)
{ //当前后缀末尾不相等时,j向前回退(连续回退,用循环)
while (j > 0 && b[i] != b[j]) //同时j要大于0保证next下标不为负数
j = next[j - 1];//j前一位next数组的值就是j要回退的下标
if (b[i] == b[j])//当前后缀末尾相等时,j+1
j++;
next[i] = j; //更新next数组的值
}
}
全部代码如下:
#include<stdio.h>
#include<string.h>
#define N 1000001
char a[N], b[N];
int next[N];
int f[N];//f数组为文本串中包含模式串的第一个位置
void getNext(int next[], char b[],int m);
void inputNext(int next[], char b[],int m);
void getf(int next[], char b[], char a[], int f[],int n,int m);
int main()
{
scanf("%s",a);
scanf("%s",b);
int n = strlen(a);
int m = strlen(b);
getNext(next, b, m);
getf(next, b, a, f, n, m);
inputNext(next, b, m);
return 0;
}
void getNext(int next[], char b[],int m)
{
next[0] = 0;
int i, j = 0;//j指向前缀末尾位置和最长相等前后缀的长度,i指向后缀末尾位置
for (int i = 1; i < m; ++i)
{ //当前后缀末尾不相等时,j向前回退(连续回退,用循环)
while (j > 0 && b[i] != b[j]) //同时j要大于0保证next下标不为负数
j = next[j - 1];//j前一位next数组的值就是j要回退的下标
if (b[i] == b[j])//当前后缀末尾相等时,j+1
j++;
next[i] = j; //更新next数组的值
}
}
void inputNext(int next[], char b[], int m)
{
for (int i = 0; i < m; ++i)
{
printf("%d ", next[i]);
}
}
void getf(int next[], char b[], char a[], int f[], int n, int m)
{
for (int i = 0, j = 0; i < n; ++i)
{
while (j > 0 && (j == m || a[i] != b[j]))
j = next[j - 1];
if (a[i] == b[j])
j++;
f[i] = j;
if (f[i] == m)
printf("%d\n", i + 2 - m);
}
}