• KMP算法(超容易理解的next数组求法)


    我想,既然知道KMP算法了,自然对于其具体如何运作也是有一定的了解的,我也没必要再大费口舌讲废话,大家一定查过很多资料,但是其实也都看的一知半解,好像自己懂了但是又没有理解透,特别是next数组的求法感觉很蛋疼,那么我就来给大家解决一下这个next数组的问题吧,如果对KMP算法没有概念的同学请先去查清楚再过来看看。

    先讲一下我们后续讲解的一些规范,我们next数组、以及模式串都是从数组的下标1开始储存的,注意不是0!(这样操作方便一点)

    下面列出一个next数组:

    模式串 A B A B A B B
    j 1 2 3 4 5 6 7
    next[j] 0 1 1 2 3 4 5

    很多人可能对这个next数组的理解就是不正确的,我们来好好解释一下:

    • 第一个元素的next值,即next[1],默认是0,这是规定的,0则表示从头开始比较
    • 那么很多人会疑惑,那上面这个例子中,j=2的时候如果不匹配,不也是应该从头开始比较吗?那么为什么next[2]不是0而是1呢?
    • 好,你可能会机智地说,next[1]=0是你规定死的,而你的模式串又是从下标为1的地方保存起来的,当然next[2] = 1啦!
    • 我们知道KMP算法的匹配是通过next数组慢慢往前回复(如果匹配失败的话)的一个过程,那么经过next数组返回后,我们究竟是从哪里开始比较呢?是从返回的那一个开始,还是它的前一个,亦或是后一个?
    • 这就涉及对next数组的真正的理解了,我们来好好理一理,这个next数组到底是怎么一回事!
    • 所谓next数组的返回,其实是前面有一部分是重叠的,才能这样回退。用文字可能很难描述,我们直接拿上面的来举例。
    • j = 4的时候,这个B前面的A和第一个A是相同的,因此next[4]=2,这个2是怎么来的呢?
    • 这个2不是因为j=2的那个B,而是因为j=1的那个A,因为j=1的A和j=3的A相匹配了,因此next[4] = 1 + 1,这才等于的2。等号右边第一个1是j=1的1,第二个1是往右边走一位的1。
    • 如果够聪明的话你应该已经看懂了,不过没懂也没关系,再来举个例子,咱们看最后一个B,即next[7]=5,如果你没理解透,就会疑惑,明明j=5时是A啊!可是如果你搞明白了,就会知道next[7]=5,是因为j=3~6ABABj=1~4ABAB匹配上了。
    • 还不明白的话,咱么可以回到next数组的本质,我们在什么时候需要使用到next数组呢?不正是在第j个位置的元素和主串的元素不匹配吗?那么我们就要会退到某个位置next[j],使得第j个位置前面的元素和第next[j]个位置前面的元素要是一样的啊!这时候我们会希望第j个位置的元素和第next[j]个位置的元素的值相同吗?不,如果相同的话那么一定、马上就会要继续查next[j]的next了,因此实际上根据这一点我们还可以对next数组进行优化,即如果第j个位置的元素和next[j]个位置的元素相等了,我们就直接让next[j] = next[next[j]],并且这么一直循环下去。当然这是一种对next数组的优化了,咱们在这里提到就行,也不细说了,能够理解的话也知道该怎么办了。

    那咱们看一下代码吧、

    求next数组的算法:

    void getNext(Str substr, int next[])
    {
        // 这个i永远只会往前走,而j会随着不匹配而逐渐等于next[j]
        int i = 1, j = 0;
        next[1] = 0;
        while(i < substr.length)
        {
            // j=0 表示当前指向的是第一个元素
            if(j==0 || substr.ch[i] == substr.ch[j])
            {
                // 前面提到过,前面的匹配,所以让后一个等于前一个的下标+1
                ++i;
                ++j;
                next[i] = j;
            }
            else
            {
                j = next[j];
            }
        }
    }
    

    下面写一下如何调用上面的函数实现KMP算法

    int KMP(Str str, Str substr, int next[])
    {
        int i=1, j=1;
        while(i<=str.length && j <= substr.length)
        {
            if(j==0 || str.ch[i] == substr.ch[j])
            {
                // 这个i指向主串。是不可能退后的
                ++i;
                ++j;
            }
            else
            {
                j = next[j];
            }
        }
        // 如果匹配成功
        if(j>substr.length) return i-substr.length;
        else return 0;
    }
    

    参考资料:《天勤数据结构》

  • 相关阅读:
    解方程
    十进制快速幂
    comb
    题单解题记录-POJ题目分类推荐 (很好很有层次感)
    算法题离谱错误合集
    VMware-Ubuntu16.04踩坑解决记录
    2020牛客多校赛第三场
    需学习的博客地址
    错误记录
    可持久化线段树 区间第k大/小
  • 原文地址:https://www.cnblogs.com/yinyoupoet/p/13287442.html
Copyright © 2020-2023  润新知