KMP算法详解

  • 什么是KMP算法

    KMP算法是用来进行模式匹配的一种算法。比如:

  • 什么是KMP算法

    KMP算法是用来进行模式匹配的一种算法。比如:
    在这里插入图片描述
    我们需要在蓝色字符串中找到黄色字符串。

    对于模式字符串匹配最简单的方法就是幼稚模式串匹配算法

在这里插入图片描述

发现不匹配指针回溯:

在这里插入图片描述

这个算法效率很低。KMP算法可以做到仅仅模式串后移,指针回溯。

  • KMP算法原理

    如果出现了不匹配,但是我们发现箭头前面的部分是匹配的。

在这里插入图片描述

而且模式串中公共前后缀。

这个时候的做法就是:

在这里插入图片描述

直接移动模式串,让模式串的前缀移动到了模式串的后缀位置。

以为移动之前左边那个部分的是匹配的,所以移动之后箭头左边的部分仍然是匹配的。

而且可以证明,如果我们对模式串取的是最长公共前后缀的时候,从将前缀移动到后缀的这个过程中不会出现匹配的情况,所以不用担心会出现漏掉某种情况。

接着走完上面的那个流程:

指针接着往后扫描:

在这里插入图片描述

发生不匹配,找最长公共前后缀,移动模式串

在这里插入图片描述

这个时候发现模式串已经超出主串的范围了,匹配失败了。

当然可以自己举一个可以匹配出的例子来看一下。

在移动过程当中,我们实际上是用不上主串的。那么我们把主串扔掉,也可以完成。

在这里插入图片描述

就拿这个模式串作为例子:将模式串的每个位置标号

在这里插入图片描述

上面的字分别代表:

如果一号位冲突,那么找最长公共前后缀,发现为0,所以让1号位与主串的下一位开始匹配。后面两种情况差不多。

然后到了4号位不匹配的话,就有了公共前后缀,长度为1,首先要移动模式串。

在这里插入图片描述

由于指针是相对主串不变的,所以这是2号位与主串当前位比较。

在这里插入图片描述

这个时候的公共前后缀为3.

可以看到这样的规律,当前模式串该用来比较的位置就是在最大公共前后缀+1的位置。

所以按照这个规律,写出所有的:

在这里插入图片描述

将这个数据放入到一个数组中,就是大名鼎鼎的next数组了:
在这里插入图片描述
通过上面的分析,我们就可以知道了,next数组描述的是当下标i处发生不匹配的时候,我们下一个应该用next[i]位置的数去和主串当前位比较。(如果下表为0,就直接从1出开始)

有了next数组之后,匹配算法可以描述如下:

首先针对模式串构造next数组

然后通过next来使模式串和主串开始比较。如果当模式串的范围超出了主串的范围,那么直接可以判定匹配失败。

  • KMP java代码实现

    public class KMPAlgorithm {
        public static void main(String[] args) {
            String str1 = "BBC ABCDAB ABCDABCDABDE";
            String str2 = "ABCDABD";
            //String str2 = "BBC";
            int[] next = kmpNext("ABCDABD"); //[0, 1, 2, 0]
            System.out.println("next=" + Arrays.toString(next));
            int index = kmpSearch(str1, str2, next);
            System.out.println("index=" + index); // 15 了
    
        }
    
        /**
         * @param str1 源字符串
         * @param str2 子串
         * @param next 部分匹配表, 是子串对应的部分匹配表
         * @return 如果是-1 就是没有匹配到,否则返回第一个匹配的位置
         */
        public static int kmpSearch(String str1, String str2, int[] next) {
            //遍历
            for (int i = 0, j = 0; i < str1.length(); i++) {
                //需要处理 str1.charAt(i) != str2.charAt(j), 去调整 j 的大小
                //KMP 算法核心点, 可以验证...
                while (j > 0 && str1.charAt(i) != str2.charAt(j)) {
                    j = next[j - 1];
                }
    
                if (str1.charAt(i) == str2.charAt(j)) {
                    j++;
                }
                if (j == str2.length()) {//找到了 // j = 3 i
                    return i - j + 1;
                }
            }
            return -1;
        }
    
        //获取到一个字符串(子串) 的部分匹配值表
        public static int[] kmpNext(String dest) {
            //创建一个 next 数组保存部分匹配值
            int[] next = new int[dest.length()];
            next[0] = 0; //如果字符串是长度为 1 部分匹配值就是 0
            for (int i = 1, j = 0; i < dest.length(); i++) {
                //当 dest.charAt(i) != dest.charAt(j) ,我们需要从 next[j-1]获取新的 j
                //直到我们发现 有 dest.charAt(i) == dest.charAt(j)成立才退出
                //这时 kmp 算法的核心点
                while (j > 0 && dest.charAt(i) != dest.charAt(j)) {
                    j = next[j - 1];
                }
    
                //当 dest.charAt(i) == dest.charAt(j) 满足时,部分匹配值就是+1
                if(dest.charAt(i) == dest.charAt(j)) {
                    j++;
                }
                next[i] = j;
            }
            return next;
        }
    }
    

    这个代码中的next数组和原理中的next数组有点不同。这里的next[i]代表前0到i个的最长公共前后缀。不过原理是一样的。
    这里生成next数组的思路我画个图来加强一下理解:
    在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值