poj 2752 Seek the Name, Seek the Fame (kmp主要问题剖析)

                         1、   kmp最小循环节问题,以及最大重复次数(参考网上大神的)

在KMP算法的使用中,首要任务就是获取一个字符串的next数组,所以我们得明白next数组的含义(最好的方法是自己弄个例子,在草稿纸上模拟一下),在这里,通俗一点讲,next[k] 表示,在模式串的 k 个字符失配了,然后下一次匹配从 next[k] 开始(next[k] 中保存的是该失配字符的前一个字符在前面出现过的最近一次失配的字符后面的一个字符的位置,有点绕口,自己写个例子看看就明白了,也可以继续往下看,有介绍,然后再自己尝试写写 )。

至于next数组为什么可以用来求重复前缀呢,而且求出来的重复前缀是最小的呢?

 

个人认为,next数组在求解的过程中,用到了KMP的思想,当前失配了,就回溯到上一个next,请见 j=next[j] ,先说个结论,如果到位置 i ,如果有 i%(i-next(i))==0 那说明字符串开始循环了,并且循环到 i-1 结束,为什么这样呢?

我们先假设到达位置 i-1 的时候,字符串循环了(到i-1完毕),那么如果到第i个字符的时候,失配了,根据next数组的求法,我们是不是得回溯?

然而回溯的话,由于字符串是循环的了(这个是假定的),next[i] 是不是指向上一个循环节的后面一个字符呢??

是的,上一个循环节的末尾是 next[i]-1 ,然后现在循环节的末尾是 i-1 ,然么循环节的长度是多少呢?

所以,我们有 (i - 1) - ( next[i] - 1 ) = i - next[i]  就是循环节的长度(假设循环成立的条件下),但是我们怎么知道这个循环到底成立吗?

现在我们已经假设了 0————i-1 循环了,那么我们就一共有i 个字符了,如果有 i % ( i - next[i] ) == 0,总的字符数刚好是循环节的倍数,那么说明这个循环是成立的。

注意还有一点,如果 next[i] == 0,即使符合上述等式,这也不是循环的,举个反例

0   1    2   3   4   5

a    b   c   a   b   d

-1   0   0   0   1   2 

下标为1,2,3的next值均为0,那么 i%(i-next【i】)=i%i==0,但是这个并不是循环。

解释完毕,然后再来看下,为什么求出来的循环节长度是最小的呢?

因为next数组失配的时候,总是回溯到最近的循环节,所以i-next【i】就是最小的循环节长度

    为什么求出来的循环次数是最多的呢?

   循环节长度是最小的了,那么循环次数 肯定是最多的了。

总结一下,如果对于next数组中的 i, 符合 i % ( i - next[i] ) == 0 && next[i] != 0 , 说明字符串循环,而且

循环节长度为:   i - next[i]

循环次数为:       i / ( i - next[i] )



 2、寻找前缀和后缀的字符串相等的位标


应该充分理解NEXT数组的含义,举例说明:

字符串: a b a b c a b a b a b a b c a b a b

  next -1 0 0 1 2 0 1 2 3 4 3 4 3 4 5 6 7 8 9

     下标      0  1  2  3  4  5  6  7  8  9 10 11121314151617 18  

    实现过程:从第18号开始出现不匹配,由于总个数一定是符合题意的(即最长匹配为其本身),然后跳到next(18),即下标9的位置说明前9(0~8)个和后9(9~17)的字符一定是前后一一对应的,否则不可能出现连续9个值变化,,然后又跳到下标next[ 9 ],即下标4 ,说明前4个(0~3)的字符与5~8的一一对应,而由上面可知,5~8的字符又与14~17的字符一一对应,所以前四个字符与后四个字符相对应,然后跳到下标next[ 4 ],即2,说明前两个和5~6对应,即和14~15对应,但我们注意到:0~1只有两个字符,而下标4的NEXT的值是2,说明前边一定有连续的0 1出现即2~3正好和0~1匹配,所以14~15也正好和16~17匹配!所以这样看2也是符合题意的!最后next[ 2 ]跳到零不符合题意。


所以验证一个定理:next    -1   0    2    4     9

即是前后字符串对应匹配的坐标

                                 坐标   0    2    4    9    18      

最后倒序输出 2 4 9 18



  •                                                               AC码

#include<stdio.h>
#include<string.h>
#define M 1001
char a[M],b[M];
int p[M],f[M];
int len1,k;
void getp()
{
	int i=0,j=-1;
	p[i]=j;
	while(i<=len1)
	{
		if(j==-1||a[i]==a[j])
		{
			i++,j++;
			p[i]=j;   
		}
		else
		{
			//f[k++]=i;
			j=p[j];	
		}
	
	}
}
int main()
{
	int i,j;
	while(scanf("%s",a)!=EOF)
	{
		len1=strlen(a);
		getp();
		for(i=len1,k=0;p[i]!=-1;)
		{
			f[k++]=i;
			i=p[i];
		} 
		for(i=k-1;i>=0;i--)
		{
			if(i==0)
			printf("%d\n",f[i])	;
			else
			printf("%d ",f[i]);
	    }         
	}
	return 0;			
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值