KMP算法学习笔记

目的

网络上搜索kmp算法的文章和视频很多,五花八门。每个人的理解都有一定不同。next数组有从1开始的,也有从0开始的等等细节,会有点不知所以。导致有的人讲完后,感觉好像懂了,但又没懂。刚接触kmp算法也老是没弄清楚,一知半解,过一会就忘了。最近终于捋清楚思路。所以写下这篇文章,主要以下两个目的。

  1. 从目前掌握的kmp知识,提供思路和想法。同时加深自己对kmp算法的理解和记忆。
  2. 让阅读者能够从这篇文章能有所收获和启发。

这里分享下学习心得:
首先我们不管kmp的代码是如何实现的,我们先要搞懂原理,原理懂了,实现有差异很正常。实现无非就是下标边界问题。这个根据个人喜好。自行选择。这里不纠结这个。这里不是说代码不重要,代码可以反过来验证原理,加深对原理的理解。

什么是kmp算法

Kmp算法是解决查找关键字的问题。给定一个字符串,要在这个文本串中查找特定的字符串,然后返回位置。
比较专业的定义参考 百度百科 kmp算法。有人可能会有疑问,为什么要有kmp算法或者说解决了什么问题,一句话效率高。

kmp算法原理

首先想下,如果没有kmp算法,要进行字符串查找要如何进行
举例
主串:ababcababc
子串(模式串):abca
朴素的想法还是很容易想到。暴力求解直接就写两个循环,主串指针mainIdx和子串指针patternIdx同时递进,然后对比,遇到不相等的时候,mainIdx回退到下一个起始位置,patternIdx回退到第一个位置。这样明显效率很低,算法复杂度为O(n*m)。代码实现略

Kmp的算法的主要思路其实就是mainIdx指针不回退,然后patternIdx指针根据next数组来进行移动或者说跳转。这里的next数组是很关键的一个数组。我们先从名字上理解next,有下一个的意思。所以这里记住next数组是干什么用的。
主串和字串进行逐个对比,在遇到不相等的情况。主串指针不变。那子串的指针如何移动?就是通过查找next数组来进行移动。

next数组生成原理

next数组生成只跟子串(模式串)有关。如何生成,原理看下图
绿色和红色表示最长相同的前后缀,一步一步直到最后一个字符。

KMP算法学习笔记_第1张图片
为什么根据next值跳转,patternIdx的移动就是正确的?
next数组每一个元素值的含义是最长的公共前后缀长度len,也是前缀刚好跳过的字符串长度
是不是刚好就是我们已经对比过无需再进行对比的字符串。文字苍白,我们看图
如果理解了下面这张图。你就知道为什么要求最长公共前后缀了。
KMP算法学习笔记_第2张图片

kmp算法代码实战

next数组代码实战

大家看了上面的next生成的原理应该还是比较容易理解,那有人就会问看图理解很简单,关键是代码如何生成,代码不会像人类那样靠眼睛一个一个对比查找最长公共前后缀。闲话少说,代码如下:

public static int[] next(String pattern) {
        char[] patterns = pattern.toCharArray();

        // 默认都是0
        int[] next = new int[pattern.length()];

        // 匹配过程中 前后缀最长的值,既是长度,也是next数组下标,前缀指针
        // 这个字段要理解,是精髓,既是长度也是前缀索引下标
        int prefixIdx = 0;
        // 后缀下标字符串第二个开始进行计算,第一个默认是0
        int suffixIdx = 1;
        while (suffixIdx < pattern.length()) {
            // 如果相等
            if (patterns[prefixIdx] == patterns[suffixIdx]) {
                // 长度加1,前缀指针前移(同时也是长度加1)
                prefixIdx++;
                // 记录当前字串截止的最长公共前后缀长度prefixIdx;
                next[suffixIdx] = prefixIdx;
                // 后缀指针前移比较下一个
                suffixIdx++;

                // 这里需要注意,这三句代码的前后顺序不能调换

            } else {
                // 不相等的情况分两种情况
                if (prefixIdx == 0) {
                    // 如果不相等,记录当前字串截止的最长公共前后缀长度prefixIdx;
                    next[suffixIdx] = prefixIdx;
                    // 后缀指针前移下一个
                    suffixIdx++;

                } else {
                    // 如果prefixIdx大于0,即长度不为零,
                    // 精髓所在,好好理解这句代码。
                    // 这句理解了,你就掌握了kmp
                    // 首先想想,如果你不这么写,你会这么写。
                    // 我的理解就是这里把模式串当主串,拿前缀当模式串。有点套娃的意思在里面。
                    // 想想next是干什么用的
                    // 主串和字串进行逐个对比,在遇到不相等的情况。主串指针不变。那子串的指针如何移动。
                    // 就是通过查找next数组来进行移动
                    // 这里是不是遇到不相等的情况了吗
                    // 前缀和后缀的字串patterns[prefixIdx] != patterns[suffixIdx]不相等,前缀(模式串)怎么移动
                    // 既prefixIdx怎么移动,不就是通过查找next数组吗?这里有点绕,多体会以下
                    // 前缀prefixIdx指针后退,后退的位置根据已知的next得知
                    // (根据已有的信息,获取下一个prefix的位置和长度,递推思想)
                    prefixIdx = next[prefixIdx - 1];
                }
            }
        }


        return next;
    }

Kmp算法完整实战

步骤如下

  1. 生成next数组
  2. 主串指针mainIdx和子串指针patternIdx,一起向前进,逐个对比
  3. 遇到不相等的情况。mainIdx不动。patterIdx如何移动,next告诉你。
  4. 遇到不相等的情况。如果patterIdx=0。则mainIdx自己加1移动。
public static void main(String[] args) {
        String mainString = "ababaaababababbababadab";
        String pattern = "ababa";

        int[] next = next(pattern);

        System.out.println(Arrays.toString(next));

        int patternIdx = 0;
        int mainIdx = 0;
        while (mainIdx < mainString.length()) {
            if (mainString.charAt(mainIdx) == pattern.charAt(patternIdx)) {
                mainIdx++;
                patternIdx++;

            } else if (patternIdx > 0) {
                // 精髓所在 想想next是干什么用的
                int nextIdx = patternIdx - 1;
                patternIdx = next[nextIdx];

            } else {
                mainIdx++;
            }


            if (patternIdx == pattern.length()) {
                // 这里比对完成 直接打印查找的位置
                System.out.println(mainIdx - patternIdx);
                break;
            }
        }
    }

总结

Kmp理解后感觉还是很简单的。技术说破不值钱。你不理解,会有人理解,这就是差距。我的建议是多找几遍别人的文章和视频,有的人说的不理解,说不定有哪个大神讲的你就恍然大悟。反复体会,然后自己实现。相信你很快也能掌握。

参考文献

最浅显易懂的KMP算法讲解

你可能感兴趣的:(Algorithm,算法,数据结构)