KMP算法:即给定一个字符串S,以及一个模式串P,求模式串P是否是字符串S的子串,如果是,返回匹配的起始下标。
字符串S模式串P
a b b a b b 该例,返回2,模式串P与字符串S从S的第二个位置开始匹配。
b a b b
注意:匹配的字符串必须为连续的字符串, 例如模式串P' abab与上述字符串S不匹配。
接下来讲next数组
字符串P a a b a a b s a a next数组 -1 0 1 0 1 2 3 0 1
- next数组存的值为----该字母前面的字符串,最大前缀与后缀的重合字母数量。
- 如上述字符串P,下标为0的next数组值为-1(人为规定,因为其前面没有字符串,没有意义)。
- 下标为1的字母前面字符串为a,只有一个字母,所以为0,最多只能匹配字符串长度-1个字符(若匹配字符串长度字符一定相等,没有意义)。
- 下标为2的字母b,前面的字符串为aa,匹配长度为1,以此类推.......
- 下标为6的字母s,前面的字符串为aabaab,匹配长度为3,图中蓝色表示前缀,红色表示后缀。
字符串P a a b a a b s a a next数组 -1 0 1 0 1 2 3 0 1
字符串S 位置i .... .... .... 位置x
字符串P a b c .... .... a b c 位置y
- 指向s的指针ptrs,指向p的指针ptrp,每次比较指针所指的两个字母是否相等。
- 假设x和y是s和p第一次不相等的位置,此时ptrs指向x,ptrp指向y。
- 暴力过程:将ptrs指向位置i+1,ptrp指向首字母串dp的首字母a。
- KMP过程,假设字符串s与字符串p红色字母a匹配的位置为j,ptrs指向j,ptrp指向首字母。即该过程由以i开头匹配字符串p变为由j开头匹配字符串p
证明:
以上述字符串S的i位置与j位置中间任何一个位置开头无法匹配出字符串P。
假设字符串S的i位置与j位置之间可以匹配出字符串P。
设该位置为k,则在字符串S中从k开头到x之前的一段,在字符串P中应该有对应的与之匹配的,可知k到x - 1的长度大于j到x - 1的长度,相当于字符串P找到了一个更长的前缀和后缀。即最长后缀不是从j开始,与假设矛盾。得证。
下面是例题与实现代码洛谷 P3375
#include<bits/stdc++.h>
using namespace std;
const int N = 1000006;
char s1[N], s2[N];
int ne[N];
int main()
{
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
cin >> s1 + 1 >> s2 + 1;
int n = strlen(s1 + 1), m = strlen(s2 + 1);
//初始化next数组
for(int i = 2, j = 0; i <= m; i ++ )
{
while(j && s2[i] != s2[j + 1]) j = ne[j];
if(s2[j + 1] == s2[i]) j ++;
ne[i] = j;
}
for(int i = 1, j = 0; i <= n; i ++ )
{
while(j && s1[i] != s2[j + 1]) j = ne[j];
if(s1[i] == s2[j + 1]) j ++ ;
if(j == m)
{
cout << (i - m) + 1 << "\n";
j = ne[j];
}
}
for(int i = 1; i <= m; i ++ ) cout << ne[i] << " ";
return 0;
}