KMP_小白解说

    KMP_归纳详解 _基础数据结

  说到kmp,其实是由三个不同的人一起得出来的算法,国内的书像严蔚敏写的那本

太过于数学化,不易于理解,其他blog上好文虽然多但是不适于初学者理解。好,废话不多说步入正题。

  在讲这个之前,先补充一个知识,字符串的前缀,后缀,前后缀公共长度。

Ps(也许有字误,请把字串自动看成子串,懒得找错字了)。。。

 

前缀:就是除了字符串的最后一个字符以外的其他的包含第一个字符的排列(顺序不可颠倒)

如:”ABCDEFG”={AAB,ABC,ABCD,ABCDE,ABCDEF}ACD这种就不是因为颠倒顺序了

同理,

 

后缀:就是除了字符串的第一个字符以外的其他的包含最后一个字符的排列(顺序不可颠倒)

如:”ABCDEFG”={G,FG,EFG,DEFG,CDEFG,BCDEFG}

 

前后缀公共长度:要使这个长度不为0,首先要满足的是他们的前后缀有公共部分。

如”ABCDAB”他的前缀,后缀可自己按照上述的方法列出,然后我们得到他的公共部分是

“AB”长度是2

 

以上是补充的知识,下面就是主题了。

KMP,为什么需要这个东西呢?

  在解决字符串的问题中,我们也许会需要用到这么一个东西,给你两个字符串string a, string b;我们需要找到ba中的位置,就是字串(其他书也叫模式)b在主串a中的位置。

  我们可以很显而易见的想出差不多on^2)的算法(最坏情况是这个),就是常见的BF方法,这个方法不需要多讲很显而易见,就是每一次去比对,如果不匹配重新从字串的第一个字符去比对。

  上面的BF用了回朔的思想,而改进这个算法,我们就是要把回朔的这一部分去掉,不再需要每一次字串从头开始,而是通过上一次的比对,如果有相同的部分,直接接到后序去比对。

  上面的废话可能初学者不是很能理解,不理解的可以不用管,下面会详细讲。

但是需要记住这个公式,(书上并没有,是各类人的归纳的)

失配之后,字串滑动的位数=已经匹配的长度-对应的部分匹配值(匹配的最后一个字符的next值)

主串:

A

B

C

A

C

A

B

A

B

C

A

B

C

A

C

B

A

B

 

字串:

 

 

现在以上述为例子,模拟KMP找寻字串在主串的位置

第一步:

 

A

B

A

B

C

A

B

C

A

C

B

A

B

A

找到第一个字串字符与主串第一个比对成功,则两者同时自增1,进行下一个比对

 

 

第二步:

 

A

B

A

B

C

A

B

C

A

C

B

A

B

A  B   

第三部:

遇到了不匹配的元素,即失配了

(主串s下标设为i=1开始,子串t下标设为j=1开始)

A

B

A

B

C

A

B

C

A

C

B

A

B

A  B   C

这里失配了S[3]!=T[3],根据上面说的公式,我们套进去,匹配的位数(红字)是2

匹配的最后一个字符是B他的next2】(B的下标)值是1,所以只向前滑动一位

(还不理解next值怎么来的,继续往下看)

第四步:

A

B

A

B

C

A

B

C

A

C

B

A

B

   A

还是不匹配,继续两个串下标ij1

第五步:

A

B

A

B

C

A

B

C

A

C

B

A

B

       A   B  C   A    C

这里简化了匹配步骤,直接匹配到s7】!=t5

这个时候我们的匹配位数(红色字体)四位,匹配位的最后一个字符的next

next4=1,所以滑动的位数=4-1=3

第五步

A

B

A

B

C

A

B

C

A

C

B

A

B

                   (A)  B   C   A   C

这个时候就已经完成匹配了。

   所以根据上面的总结,KMP的思想就是不去回溯前面的比对,每次比对根据上一次的比对来得到下一次比对的位置,而不是每次从1开始。

上面的内容中,有一点是精华,我们怎么样得出next[]数组的值的

也就是每一次失配之后,我们应该从哪个子串的位置开始。

   这个时候就需要用到开头说的前后缀公共长度了,(此方法书中没有,乃本人所归纳得出,看过其他大牛的blog还没有看到和我一样的方法的)

我说的方法是从下标为1开始的算法(同严蔚敏中的)(blog上大多用下标从0开始)没差别。

   首先,求next数组,我们得出这么一个结论,他的值只取决与子串的本身,与主串没有关系。至于为什么?自己多举几个例子来看看就知道了,比对的是只和子串有关。

因此,根据上面的例子中,A B C A C

next[1]=0,(规定next[1]必定为0,因为是起始的第一个点)next[2]=1,next[3]=1,next[4]=1

看到这里,也许会好奇怎么都是1呢?别急,我在举一个例子

A B A A B C A C

Next[1]=0,next[2]=1,next[3]=1,next[4]=2,next[5]=2,next[6]=3,next[7]=1,next[8]=2;

原因:当我需要求next[i]的值的时候,我们只需要把第i个字符当作失配的(子串与子串本身匹配),然后我们再判断1i-1这个长度的字符串中他的公共前缀后缀长度是多少(记为lenth),然后把的出来的lenth+1,就是当前nexti】的值了,即next[i]=lenth+1;

如例子,next4】因为ABA他的公共前后长度为1,再加1,就是2;以此类推。不再枚举。

理由就是刚才前面的图片,只需要把主串也当做子串,用字串自己匹配自己,得到他的next值。相同的部分我们就进行滑动,next就是这个作用。

 

好了上面就是详情,若不清楚则是我的表达还不到位。

 


#include
   
   
    
    
#include
    
    
     
     
using namespace std;
int next[100];
 int get_next(string s){
 	int i=1,j=0;
 	next[1]=0;
 	while(i
     
     
      
      =t.length()){
			return i-t.size()+1;//书上用的是结构体,我这里用数组所以a[0]放一个填充(先用string,好写) 
		}
		else return 0;		 	
 } 
 int main(){
 	string s,t;
 	
 	cin>>s>>t;
 	get_next(t);
 	int position=index_kmp(s,t,1);
 	if(position){//position返回的是下标 
 		cout<<"find it "<
      
      
       
       <
       
       
      
      
     
     
    
    
   
   

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值