关于初学kmp算法的一点看法

 先写这么多,有时间了整理思路好好写一下自己的看法。

我这里的代码与标准的哪些next数组代码不一样,精简版只有5行代码量,浓缩的都是精华,但我认为这种开始更便于理解,那些精简的代码主要是将next数组变换了一下,定义也不一样,我这里是next [ i ] 表示的长度为 i+1 的字符串公共前后缀长度,其他的一般是把我这种结果的 next 数组向右平移了一位对齐,next [ 0 ] =  -1,next [ i ]  = len的意思是长度为 i 的字符串公共前后缀长度为len,这样可以对代码做出改进,但我们暂时不讨论这么多,后续再优化

代码如下,代码里对next数组求法做了一些注释,但关键还是没有说明,有些人一直卡在不知道什么地方,绕进去出不来了,可能只要点一下某一步就豁然了。有几点我说一下,我们可以观察到求next数组和kmp整体代码结构相似极高,这里多思考一下,其实求next数组的方法就是在已知了第一个元素没有前后缀next值就是0,然后利用这个求第二个,就这样一步一步推。我们将模式串复制为两个,用两个指针分别指向他们的各个元素从头比较,如果相同,指针各加一,此时next值就是前一个元素的next值加 1,这个很多博客都说明了我就不再赘述;关键是不同的时候,一旦不同,那么是否代表了没有公众前后缀了呢?此时next值难道就为0了吗?这时候就是关键了,我们就得回想到KMP这个算法的思想!!!就是在回退指针时不回退所有!!!有没有可能有部分相同的地方(到这里很多人可能被绕进去了,不是说已经比较不一样了吗,怎么又来个再找部分一样的),我这里举个例子,ab ab ab ac       ab ab ab ab   ,这是一个字符串abababacabababab,在这里,对这个字符串整体来说最大公共前后缀是abababa,然后到那个c与最后这个比较的时候,不一样了,那么代码里的指针回退到前一个元素的next值处(精简版是直接等于该元素next值),此时就是把眼光看回ababab这个公众前后缀!!!!它自身里面是否还有公共前后缀!!!比如这里,先看abababac里的aba b aba       c,(我把c隔远了先不看因为就是到他断了,我把眼光看到前面已经配对的)我们发现aba aba这一对公共前后缀,再者,我们是不是已经知道这段字符串是和abababab里的abababa是匹配的!那后面这对也是有公共前后缀aba aba这四个就是等价的!!此时就是要把最后一个字符b移到前面来与aba这段比较短的公共前后缀的下个字符再比较!!这就是关键!!!而他前一个元素的next值就是表示这串公共前后缀多长,比如这里就是3,再由于我们数组都是从0开始计数,所以当这个next值作为数组下标的时候就是刚好指在了这串字符的下一个位置!!!也就是第四个元素的位置,然后从这里开始比较!!!比如这里就是aba  b ,最后一个b与这个b比较,刚好一样。这里非常巧妙,所以才会有那行最让人莫名其妙的代码,暂时就说这么多,改进代码稍后再看,初步理解kmp的next求解过程即可

 

看图

#include<stdio.h>
#define MAXSIZE 10
typedef struct
{
    char *ch;
    int length;
}String;

void GetNext(String P, int next[])//此处的next[i]表示的长度为 i+1 的字符串公共前后缀长度
{//求next数组的过程本质上就是利用kmp思想将模式串与自己比较来递推next数组,可以发现此代码和kmp代码形式即为相似
    int i = 0, j = 1;//两个指针,想象将模式串复制为两个,i指向其中一个模式串的第一个元素(就是把他当作主串),j指向另一个模式串(当成真正的模式串与前述的那个比较,我们求next数组主要就是看这个)的第二个元素(因为我们默认了第一个元素的nextz值就是0,所以从第二个开始)
    next[0] = 0;//默认第一个元素无前后缀即为0
    while(j < P.length)//循环条件
    {
        if(P.ch[i] == P.ch[j])//如果两指针比较相同
        {
            next[j++] = ++i;//这里有好几步,分开看就是先next[j] = i+1; 然后i++;j++; 再进入下一对字符比较
        }
        else//如果不相同,要做的操作就是回退指针i,先别多想啥意思,继续看
        {
            if(i > 0)//防止回退过头了
            {
                i = next[i - 1];//这里是关键,意思就是i指针回退到前一个元素的next值指向的地方,很多人对这里不懂,暂时搁置下
            }
            else
            {
                next[j] = 0;//这里还可以这样写 next[j] = i;其实此时i就是0, 如果i一直回退到第一个next值也就是next[0],那就代表没有任何公共前后缀,那此时j指向的元素next即使0;如果懵了不要紧,先看下去
                j++;//此时我们知道i回到了第一个元素,再j还没++前,j指向的next值是0,此时就得重新开始比较,前面的各个元素公共前后缀都没啥影响了,就是断了,那此处j就指向下一个新元素,两对再从头开始
            }
        }
    }
}
int KMP(String S, String P)
{
    int i = 0, j = 0;
    int next[MAXSIZE];
    GetNext(P, next);
    while(i < S.length && j < P.length)
    {
        if(S.ch[i] == P.ch[j])
        {
            i++;
            j++;
        }
        else
        {
            if(j > 0)
            {
                j = next[j - 1];
            }
            else
            {
                i++;
            }
        }
    }
    if(j == P.length)
        return i - j + 1;
    else
        return 0;
}

int main()
{
    String P;
    P.length = 6;
    P.ch = "ababca";
    String S;
    S.length = 14;
    S.ch = "abaababdababca";
    int next[MAXSIZE];
    GetNext(P, next);
    for(int i = 0; i < 6; i++)
    {
        printf("next[%d]= %d  ", i, next[i]);
    }
    printf("\n");
    if(!KMP(S, P))
    {
        printf("no");
    }
    else
    {
        printf("pos = %d ", KMP(S, P));
    }


}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值