KMP算法

1、基本概念

KMP算法是一种高效的字符串匹配算法,算法名取自三位共同发明人的名字首字母

应用场景:子串在主串中的定位。比如求子串在主串中出现的起始位置,子串在主串中出现的次数等等。

2、算法核心

2.1:暴力求解

定义主串指针i(初始化0), 子串指针j , 不断比较主串和子串每个字符是否一致,如果一致,继续比较,如果不一样,主串指针变成1,子串指针变成0,从新开始比较。可以看出,整个算法时间复杂度为O(m*n),效率是比较低的。

2.2, KMP算法

KMP其实是对上面的暴力求解进行了改进,利用的是匹配失败之前,匹配成功的信息。原则是保持主串的指针i 位置不回退。

如图,匹配失败之后,保持指针i 不变,尽可能移动子串的指针到有效的匹配位置(这个意思是指,i前面两位已经匹配成功了,不需要额外进行比较了。此时只需要比较 i 和 j 两个位置的字符就可以了。)

进一步解释:

  1. 如图 子串 index=5 的时候,匹配失败,但是前面 5位是已经匹配成功了的(已知信息)。
  2. KMP算法固定主串指针i 不回退,那么很明显就需要变更子串的指针j。如何知道j 要变到哪个位置呢?如果变成0,看图中的情况肯定是不正确的,这是因为子串前5位中,存在前缀AB 和后缀AB,这两个是一摸一样的,下次匹配可以跳过AB的匹配,这样可以减少两次对比。
  3. 因此,当子串index=j 匹配失败时,我们只需要知道 index = j-1 位置相同的最长的前后缀长度,就可以找到指针 j 需要回退的位置了。

通过上面的分析,其实问题可以先简化为 求 子串 对应index坐标 最长前后缀的长度即可。网上一般叫做求 next数组。

求next数组的思路可以参考如下博客

https://blog.csdn.net/qq_45910820/article/details/134877075

下面给出相关的代码。其实求next数组,可以看做是两个相同的字符串自己和自己比较。

    /**
     * 模板字符串的next数组:
     * next数组每个值的含义:当前字符之前的字符串中,最大相同前缀后缀的长度
     * 比如 ababaca ---》 [0, 0, 1, 2, 3, 0, 1]
     */
    public static int[] getNext(String str) {
        int length = str.length();
        int[] next = new int[length];

        // 初始化第一位的长度为0;
        next[0] = 0;
        // j代表最大前缀长度,初始化为0,这里一起作为指针辅助遍历。
        int j = 0;

        // 这里有两个指针,i 和 j ,其中i 是不断往后遍历,j会涉及到回退
        // j 始终是要比 i 小的,因为求某个坐标的前后缀长度,要小与前面字符串的长度,否则就没有意愿了。
        byte[] bytes = str.getBytes();
        for (int i = 1; i < length; i++) {
            // 字符不一样
            while (j > 0 && bytes[i] != bytes[j]) {
                // 这一步要好好理解一下。
                j = next[j - 1];
            }
            // 字符一样,说明相同的前后缀长度加1
            if (bytes[i] == bytes[j]) {
                j++;
            }
            // 将长度赋值给当前坐标
            next[i] = j;
        }
        return next;
    }


    public static int KMP(String str, String sub) {
        if (sub.isEmpty()) {
            return 0;
        }

        // 获取next数组
        int[] next = getNext(sub);

        // 定义指向子串的指针
        int j = 0;
        for (int i = 0; i < str.getBytes().length; i++) {
            // 如果当前位置字符串不匹配,利用最长前缀表跳过已经匹配成功的部分
            while (j > 0 && str.charAt(i) != sub.charAt(j)) {
                j = next[j - 1];
            }

            // 如果当前位置匹配,那么就继续向后匹配
            if (str.charAt(i) == sub.charAt(j)) {
                j++;
            }
            // 如果子串匹配完成了,那就返回起始位置的坐标。
            if (j == sub.length()) {
                return i - sub.length() + 1;
            }
        }
        // 子串没有匹配成功,返回-1
        return -1;
    }

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值