尚硅谷韩顺平KMP算法兼补充很详尽的KMP算法

引子

在韩顺平灾难性的讲解下我并没有搞懂什么叫KMP算法,在他的推荐下我去看了看博客https://www.cnblogs.com/zzuuoo666/p/9028287.html的内容,确实是有所帮助,将韩顺平没有讲清楚的next数组递归过程点出来了。但是对于我最最关心的next数组如何生生成的细节则说的还是有一些不太清楚。文中虽然举了两个例子,但是这两个例子尤其是第二个例子试图说明“能在前缀中找到字符的例子”时举例还是有一些极端,我在下文补充一个例子。

关于next数组

以下的内容就接着博客https://www.cnblogs.com/zzuuoo666/p/9028287.html的内容去进行叙述。

image

对于字符串的前缀和后缀我画了上面的图来阐述:

首先,最长的两条绿色线段表示正在成功匹配的前缀和后缀,类比到下面的字符串,就是红线加粗强调的部分。但是继续匹配时,发现F和E无法完成匹配了(正如下面的图),上面图的体现方式是出现了一小段红色的不和谐,我拿紫色的圈圈圈住了这段不和谐。这种情况下,当正在匹配成功的前缀和后缀无法继续匹配成功了。这时候我们需要做什么呢?

这时候就涉及到KMP算法最让人难以理解的部分,即:

k = next[k]

其中该博客原文是这么描述的:

那为何递归前缀索引k = next[k],就能找到长度更短的相同前缀后缀呢?这又归根到next数组的含义。我们拿前缀 p0 pk-1 pk 去跟后缀pj-k pj-1 pj匹配,如果pk 跟pj 失配,下一步就是用p[next[k]] 去跟pj 继续匹配,如果p[ next[k] ]跟pj还是不匹配,则需要寻找长度更短的相同前缀后缀,即下一步用p[ next[ next[k] ] ]去跟pj匹配。此过程相当于模式串的自我匹配,所以不断的递归k = next[k],直到要么找到长度更短的相同前缀后缀,要么没有长度更短的相同前缀后缀。如下图所示...

应该说,这个递归过程博主说得很准确。当我们令k = next[k],k代表的字符立即会由F变为E(即下面图序号2旁边的F变为我拿红圈圈圈住的左边的E)。这时我们会发现一个很神奇的事情,即序号1,2,3指出的ABCD字符串都是相同的,因为序号2等于序号3,序号1等于序号2,可以推知序号1等于序号3(这个等式对应的稳态很重要,因为这个等式指明了为什么不会存在更长相同前缀后缀的原因,因为如果存在更长的,这个等式也成立,则通过k = next[k]同样会找到这个更长的)。这时,如果两个圈住的E是相等的,则一定可以确定我们可以得到一个更小的匹配ABCDE。如果这时候两个圈住的E是不相等的,我们则需要进行进一步的递归,对应到图一,即对序号4指代的蓝线进行进一步的递归,重复上述操作。

韩顺平给出的代码是这样的:

//获取到一个字符串(字串)的部分匹配值
    public static int[] kmpNext(String dest) {
        //创建一个next数组保存部分匹配值
        int[] next = new int[dest.length()];
        next[0] = 0; //如果字符串长度为1则部分匹配值就是0
        int j = 0;
        for (int i = 1; i < dest.length(); i++) {
            //当dest.charAt(i) != dest.charAt(j),我们需要从next[j - 1]获取新的j
            //直到我们发现有dest.charAt(i) == dest.charAt(j)才退出
            while (j > 0 && dest.charAt(i) != dest.charAt(j)) {
                j = next[j - 1];
            }

            //当dest.charAt(i) == dest.charAt(j),部分匹配值就加一
            if (dest.charAt(i) == dest.charAt(j)) {
                j++;
            }
            next[i] = j;
        }
        return next;
    }
posted @ 2021-05-15 01:27  imissinstagram  Views(416)  Comments(1)    收藏  举报