浏览过很多的博客,发现主要有两种的求next数组的方法:
第一种:F数组(也就是next数组)表示到第i个字符所构成的最长前后缀的长度减一,这样得到的next数组里面的数也是模板串的下标。
int main()
{
char B[100];
gets(B);
int m=strlen(B);
int F[100]={-1};
for (int i=1;i<m;i++)
{
int j=F[i-1];//j指B数组的下标
while ((B[j+1]!=B[i])&&(j>=0))
j=F[j];
if (B[j+1]==B[i])
F[i]=j+1;//F[i]=F[j]+1;
else
F[i]=-1;
}
//GetNext(B,F);
for(int i=0;i<m;i++)
cout<<left<<setw(3)<<F[i];
cout<<endl;
return 0;
}
下面是较多出现的
void GetNext(char* p,int next[])
{
int pLen = strlen(p);
next[0] = -1;
int k = -1;
int j = 0;
while (j < pLen )
{
//p[k]表示前缀,p[j]表示后缀
if (k == -1 || p[j] == p[k])
{
++k;
++j;
// if(p[j]!=p[k])
next[j] = k;
// else
// next[j]=next[k];
}
else
{
k = next[k];
}
}
}
先说一下第二种求next数组,优化过和不优化的next数组值是不同的比如abcac,得到就不同;但是next数组里面存的都是在失配后需要倒回的下标。第一种得到的next是最长前后缀的长度减去1,两者的next数组含义是不同的,因此得到的数组也就是不同的。推荐第二种优化的;
下面是求kmp的模板:
int KMP(char *str, int slen, char *ptr, int plen)
{
int *next = new int[plen];
cal_next(ptr, next, plen);//计算next数组
int k = -1;
for (int i = 0; i < slen; i++)
{
while (k >-1&& ptr[k + 1] != str[i])//ptr和str不匹配,且k>-1(表示ptr和str有部分匹配)
k = next[k];//往前回溯
if (ptr[k + 1] == str[i])
k = k + 1;
if (k == plen-1)//说明k移动到ptr的最末端
{
//cout << "在位置" << i-plen+1<< endl;
//k = -1;//重新初始化,寻找下一个
//i = i - plen + 1;//i定位到该位置,外层for循环i++可以继续找下一个(这里默认存在两个匹配字符串可以部分重叠),感谢评论中同学指出错误。
return i-plen+1;//返回相应的位置
}
}
return -1;
}
两种方法得到的结果是一样的,但其中都有一个共同点,就是当其自身匹配自身不相等的情况时,都有一句k=next[k](第一种是F数组),为什么?链接有解释,包括为什么next用-1开头,为甚用前后缀求:还有优化第二种next求法及其证明
kmp算法是将一段字符串分成子串进行求最长前后缀的,因此他有一个性质,就是做这道题传送门
有了kmp算法的next数组(保存某段子串的相同前后缀的最大长度,不包括自身),可以从最长的相同前后缀开始考虑,对于串S,根据next[strlen(S)-1]得到的最长相同前后缀为S1,则再根据next[strlen(S1)-1]得到的S1的最长相同前后缀S2肯定也为S的相同前后缀。(通过画图可以很容易的证明)。
这样,通过不断的向前查找next数组,得到一系列的相同前后缀。下面证明这些前后缀包含了所有的相同前后缀:
由于我们得到的是一系列最长相同前后缀,假设有一个相同的前后缀Sk不属于这些通过next递推得到的前后缀集合,那么len(Sk)必定小于len(S1),且Sk必定为S1的子串(通过画图很容易看出来);此时若Sk是S1的最长前后缀,则与前提Sk不为某一子串的最长前后缀
矛盾,那么Sk不是S1的最长前后缀,则可以退出len(Sk)必定小于S2,且Sk必定为S2的相同前后缀..... 数学归纳递推下去,可以知道Sk要么为某个Si的最长前后缀,要么len(Sk) = 0. 这样可以证明这些通过next数组递推得到的前后缀包含了所有的相同前后缀
举个例子
字符串:ababcababababcabab
1、长度为 18 时,字符串为 ababcababababcabab,前缀 ababcabab,后缀 ababcabab,输出18;
2、长度为 9 时,字符串为 ababcabab,前缀 abab,后缀abab,输出 9;
3、长度为 4 时,字符串为 abab,前缀 ab,后缀 ab,输出 4;
4、长度为 2 时,字符串为 ab,前缀 a,后缀 b,此时前后缀不相同,输出此时的字符串长度 2,并结束;
(因为字符串是从后向前一一查找判断的,所以当找到不想同的前后缀时既查找完毕,输出并结束)
......
如果还不明白,我就仍然以上述例子说,next[18]=9;意思是说,此字符串有长度为 9 的相同前后缀, 如上 1 所示,
设字符串长度为 9,此时next[9]=4,表示在此长度为 9 的前缀中,又有长度为 4 的相同的前后缀,
设字符串长度为 4,此时next[4]=2;.............
依次进行....