KMP最难理解的就是next[],
next[]表示往回翻的位置,也等同于当前位置下(好理解,可以只理解这一种的代码写法)||当前位置的前一个下,前缀=后缀的最大个数。
其实next[]数组举好例子就很好理解:
首先定义一下:next的意思就是指大的字符串前缀==后缀,我们把该前缀或者后缀称之为小字符串,那么小字符串里还有前缀==后缀的串,我们称之为小小字符串,小小字符串中还有小小小字符串……那么当遇到字符串不匹配的情况下,我们要往前翻,翻到比自己小一号的字符串前缀末尾,去判断它的下一位是否等于a[i](后缀不匹配的字符)。
画图:
1 2 3 3 3 3 2 2 3 3 3 3 2 1
[ [ [ ]z [ ] ] x... [ [ ] [ ] ] ] y
//1表示一个大的字符串,2表示小字符串:1的前缀==后缀,也就是2围城的括号内容相等,
3表示2的小字符串,里面内容相等,当1串中x!=y,也就是2+x!=2+y,那么我们将x的前
一个字符往回翻,得到第一个3串末尾字符,在判断第一个3串的下一个字符z是否等于y,
为什么可以这样判断呢,在于四个3串都相等,那么第一个3串==最后一个3串。
因此next一直在取前缀等于后缀的小串,直到取到一个小串的下一个字符等于大串尾字符。
举个例子:
abcabdabcabc
分两种情况:
1.数组下标从1开始(容易理解的例子)
2.数组下标从零开始
1.情况一
0,1,2,3,4,5,6,7,8,9,10,11,12 下标
a,b,c,a,b,d,a,b,c, a, b, c 字母
0,0,0,1,2,0,1,2,3, 4, 5, 3 next[]
解释一下例子:
字符数组中最后一个”c”字母如果是“d”,那么next[12]将会是6,但是前缀最后一个字符不等于后缀的字符,由字母”d”的前一位就要往回翻(为什么是前一位等同于为什么判断a[j+1]?=a[i]是一个道理,等会解释),也就是翻到下标为2的地方字母为“b”的地方,然后再次判断a[j+1]?=a[i],可以看出前后缀都是abc,那么next[12]=3;
next[]的含义就是在当前位置下,前缀==后缀的最大个数;另一个含义是往前翻到的地方正好是当前子字符串中前缀等于后缀的前缀末尾字符的下标。
解释为什么是a[j+1]?=而不是a[j]:
我们用j表示前缀,用i表示后缀指向,那么如果a[前缀]!=a[后缀],那么前缀就要往前翻,翻到的地方一定还是前缀==后缀处位置,再判断它的下一个位置字符是否相等(a[j+1]?=a[i]),因此判断的是j+1位置。
代码:
#include<cstdio>
#include<cstring>
char s[1005];
int next[1005],n,j;
int main()
{
scanf("%s",s+1);
for(int i=2,j=0;i<=strlen(s+1);i++)
{
while(j>0 && s[j+1] != s[i]) j=next[j];
if(s[j+1]==s[i]) j++;
next[i]=j;
}
for(int i=1;i<=strlen(s+1);i++)
printf("%d ",next[i]);
}
1.情况二
0,1,2,3,4,5,6,7,8,9, 10,11,12 下标
a,b,c,a,b,d,a,b,c, a, b, c 字母
-1,0,0,0,1,2,0,1,2, 3, 4, 5 next[]
定义一个j记录前缀的下标,一个i记录后缀的下标。
另一个含义就是在当前位置的前一个位置下,前缀==后缀的最大个数。
代码大同小异,也是前缀末尾前一个字符往前翻,翻到子串相同位置:
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
char s[1001];
int next[1001];
int main()
{
scanf("%s",s);
next[0]=-1;
for(int i=1,j=-1;i<strlen(s);i++)
{
while(j>-1 && s[i]!=s[j+1]) j=next[j];
if(s[i]==s[j+1]) j++;
next[i+1]=j+1;
}
for(int i=0;i<strlen(s);i++)
cout<<next[i]<<" ";
}