Kmp算法学习笔记

KMP算法我认为难点在next数组的建立。 看Kmp前我仔细看了下传统的匹配模式,代码为:(首元素存放串的长度)

int Index(SString S,SString T)

{

int i = 1;

int j = 1;

while(i <=s[0] && j<=T[0])

{

if(S[i] == T[j])

{

i++,j++;

}

else

i = i-j+2;

}

if(j > T[0])

return i-T[0];

else 

return 0;

}

我认为想深刻理解好KMP算法要和传统匹配匹配结合起来。KMP无非就是利用next数组的信息,即最长前缀子串的长度来令j的值回溯,而不是像传统匹配那样令i的值回溯。

int Index_Kmp(SString S,SString T)

{

int i = 1;

int j = 1;

int next[200];


Get_next(next);

while(i <=s[0] && j<=T[0])

{

if( j==0 || S[i] == T[j] )

{

i++,j++;

next[i] = j;

}

else

j = next[j];

}

if(j > T[0])

return i-T[0];

else 

return 0;

}

Kmp最主要的就是next数组的建立,这也是kmp的难点,也是困扰了我很久的地方。

先敲遍next数组的函数以找回感觉:

void Get_next(char *t,int *next)

{

int i = 1,j = 0;   //这里我认为是挺重要的,当j为0时,代表i和j都要+1从新的位置重新开始匹配,而next的第一个元素的值是已定的,为0.


next[1] = 0;

while(i <= t[0])

{

if(j == 0 || t[i] == t[j])   /*if语句判断为真的情况有两种:

情况1. 当j==0时,后面的 t[i] == t[j]不用管,因为j==0成立后面的判断就不会执行,也避免了越界问题,

    j==0相当于在表明在当前i不变的话,下标j已经回溯到尽头了,不可能再有下标为 j 的字符与下标为i的字符相等。

    j不能再回溯了,而想继续匹配的方法就只有令i指向一个新的字符, j重新指向串的首元素,重新再开始匹配。而

    表达式i++则令i按顺序指向了一个新的字符,j++后j的值1就相当于重头开始匹配

情况2:当j!=0但t[i] == t[j]时,这种情况下说明有两个字符匹配相等,先假想是第1个(假设j=1)与第i个字符匹配了,那么就表明了

      此时的最长前缀肯定不为0。而令最长前缀不为0的策略则就是先让j++,若j原本等于0,则最长前缀就变为1。i++的

      意义是使i往前一位,因为第i个字符下的最长相等前缀的长度是储存在next数组的[i+1]里的,所以令i向前一位,然后

令next[i] = j;即最长相等子前缀的赋值

*/

{

i++,j++;

next[j] = i;

}

else   //当 j != 0 && t[i] != t[j] 时,既然当前字符不相等了,那就令j回溯到可能相等的位置再匹配,记住,是可能相等。若再不相等则再回溯,直至j回溯到尽头

j = next[j];   //此步是令j回溯,可以将next数组的建立看为从第i个字符开始的串t,与从第j个字符开始的串t 匹配。

}

if(j > T[0])

return i - T[0];

else

return 0;

}

其实还有一点就是因为这里运用的串的数据结构T[0]或S[0]的存放的是串的长度,且串的起始下标是从i开始的,而平时用字符串的话我用的都是从0开始的,且长度是直接用字符串函数strlen()计算出来。若用数据结构,则上述的代码就有了一点改变,但总体结构还是一样的,我是比较完两者的差别,然后才学会了KMP的。

下面贴出普通类型字符串的KMP算法(红色字体为有一些值得注意的细节改变):

int Index_kmp(char *s,char *t)   //字符串s和t的下标都是从0开始

{

int i,j,len1,len2,next[200];


len1 = strlen(s);

len2 = strlen(t);

i = j = 0;

Get_next(next);

while(i<len1 && i<len2)

{

if(j == -1 || s[i] == t[j])  //具体先看完该版本的Get_next函数,那个懂了,这里也懂了

{

i++;

j++;

}

else

j = next[j];

}

if(j == len2)

return  i-j;

else 

return -1;   //下标从0开始,所以不能return 0 

}


void Get_next(char *t,int *next)

{

int i,j,len;


len = strlen(t);

i = 0;        /*这里的i和j的值分别设为0和-1.  这里串的下标是从0开始的,所以必须有个不在next数组下标范围内的值来标识j的值已回溯到尽头,这就是j=-1的第一个功能

第二,i的初始值要比j大1,试想第一次判断语句肯定是要成立的,因为next[1](next的第二个元素)的值其实已经确定了,只不过我们将之放入了循环进行统一设置而已,从第三个元素开始才会出现两种或以上的情况。而第一次执行了if内的语句,i++后的值i一定要等于1,因为下标1代表next数组的第二个元素,而j设置一个刚好小于最小下标的标志值用于判断是否已回溯到尽头。那么第一次的执行结果肯定是i=1,j=0,next[i] = j了,即使next[1] = 0.

这里好像出现问题了,因为按照之前的那个程序next[1]的值是要等于1,next[0]=0的,可是没关系,这样做刚好和字符串的起始下标的改变相适应了。而在前面的Index_kmp函数的 j=-1; 也是因为这原因。

*/

j = -1;    

next[0] = -1;     //此时的next数组下标从0开始,所以第一个元素的值不能赋为0了,而是刚好比0小1的元素,这么做之后,除了首元素,next的其他位置的

  //值都为当前位置下最长前缀的长度,而不是之前的最长前缀+1.

while(i < len)

{

if(j==-1|| t[i]==t[j])

{

i++;

j++;

next[i] = j;

}

else

j = next[j];

}

}

写一个例子来提醒记忆:子串为ababaaaba  n=9.

若串的下标是从1开始的,那么next数组的值分别为 next[1...n] =      0,1,1,2,3,4,2,2,3

若串的下标是从0开始的,那么next数则的值分别为 next[0...n-1] = -1,0,0,1,2,3,1,1,2 


以上是我自学KMP时的一些心得体会和方法,当时KMP算法困了我很久,而且一旦长时间不用就容易忘记,所以写篇博客来加深印象,也有利于以后的复习。

我知道自己有些地方写的不是那么正确,希望看见的朋友能够指出并共同探讨,我也只是一个刚学数据结构的菜鸟。这也是我csdn上的处女博客,若真有看不过眼的地方请不要喷我,很打击自信的...  更希望能在csdn上认识更多志同道合的朋友一起学习,探讨问题!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值