KMP算法详解及代码

KMP算法详解及代码

最近正好在看字符串相关的算法内容,就顺便把KMP算法回顾了一下。相应的代码和联系在下面。

定义及应用

定义:KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt提出的,因此人们称它为克努特—莫里斯—普拉特操作(简称KMP算法)。
常见应用场景:在判断某一个字符串中是否出现了另一个字符串的时候。
如我们需要判断:字符串A”adadfhnn“ 是否包含字符串B”adfh“ 的时候就可以使用。
如果按照
暴力算法
的复杂度就是 A.length * B.length ,需要两层循环。
而我们
KMP算法
的复杂度只要**(A.length + B.length)**

理论

基本概念

首先我们来看一下正常的暴力算法是怎么进行字符串的匹配的:
我们在字符串A:”ABCABCDHIJK“ 中搜索字符串B:“ABCE”,
在这里插入图片描述
在i=3的位置发现不匹配,就会将i指向 0 + 1,j 指向第一个位置 0.如何重新进行挨个匹配。这样的复杂度就是两个字符串长度的积。
在这里插入图片描述
然后我们在看一下KMP算法
字符串的索引 i 向前匹配的时候,如果碰到不匹配的字符。我们会考虑,先不改变 i 的位置,直接调整 模式字符串的索引位置 j ,如下图能够发现就算 i 从 第二个位置再和模式字符串按个重新匹配,前两个位置也是匹配不上的。
在这里插入图片描述
我们可以直接让从 j 从 第二个位置开始,这样两个字符串就都是从A开始进行匹配了。相当于,i的位置没变,只对 j 的位置进行调整。
在这里插入图片描述
而问题的关键就是我们该如果调整 j 的位置。

next 数组

在KMP算法中会先对模式串进行计算。挨个计算不同位置的最大相同前后和缀

**前缀:**前缀就是指一个字符串中除了最后一个字符外,从前往后所有的字符串。
如:ABAD的前缀就有:A , AB , ABA
**后缀:**同样的道理后缀就是指一个字符串中除了第一个字符外,从后往前所有的字符串。
如:ABAD的前缀就有:D , AD , BAD

而next数组就算记录 ABAD中的每个位置的最大相同前后和缀长度。
当 计算第一个位置的值是 A,就一个字符所以没有前后缀,肯定就是 0 ,
当 计算第二个位置的值是 AB,前缀为A,后缀为B,前后缀不相等,所以没有最大相同前后和缀 ,长度也为0
当 计算第三个位置的值是 ABA,前缀有A,AB ;后缀有A ,BA,所以存在相等的前后缀A ,所以长度为1
当 计算最后一个位置的值是 ABAD,前缀有A,AB ,ABA ;后缀有D,AD,BAD,所以没有最大相同前后和缀 ,长度也为0.
最后计算出来的next数组就是:[0,0,1,0 ]

详细视频可以看一下这个up主的视频,挺详细
KMP算法视频链接

总结

当我们计算出next数组后,回到我们开始匹配的地方
在这里插入图片描述
当我们有字符不相等的时候,就会查询这个字符前一个位置在next数组中的值,再由 j 指向该位置。如上图:C 和 D 不相等时,就查前面第三个位置在next数组的值:next【2】= 1.
这是时候 i 不动,j 指向 j = 1 ,也就是B的位置,再继续比较就行。

注意

我们在计算next数组的时候通常会有三种不同的取值方式。
[0,0,1,0 ]
[-1,-1,0,-1 ] :全部减一
[0,0,0,1 ] : 全部往右移一位
其实概念是一样的,我们的next数组在取值的时候,会取 j-1 个位置的值,第三中就可以直接取当前j的位置的值,第二种就是会将取到的值加一。其最后得到的都是同一个位置。

###进阶
当我们的模式串为”ABAB“时,我们在匹配的时候,我们会发现当B和C不相等,按照next数组的位置,j 会指向 next[2] = 1. 这时候 j 又指向了B,显然这并没有意义,因为刚刚B和C已经比较过了。所以通常这种情况可以加以判断,如果 sting[ j ] == string[next[j-1]] ,就直接再往前推,j 等于前面这个B前一个位置在next数组中的值。
在这里插入图片描述
在这里插入图片描述
推荐练习是
Letecode 中的 28. 找出字符串中第一个匹配项的下标。是一道典型的用KMP算法来就行字符串匹配的题目。
这道题的解析:添加链接描述
Letecode 中的 459. 重复的子字符串 是相关的应用,有一点变通。
可以看一下我的解析:
这道题的解析

代码

代码实现(Java):


```java
public int[] setNext(String s) {
        //将字符串转化成数组。
        char[] chars = s.toCharArray();
        //判断字符模式串是否为空。
        if (s.isEmpty()){
            return null;
        }
        //如果只有一个字符则直接返回数组。
        if (s.length() == 1){
            return new int[]{0};
        }

        //初始化,j指向第二个字符,因为第一个字符的最长相等前后缀长度一定是0。
        int j = 1;
        int i = 0;
        int[] next = new int[s.length()];
        next[0] = 0;

        while (j < s.length()){
            if (chars[j] == chars[i]){
                i++;
                next[j] = i;
                j++;
            }
            else {
                //如果i和j不相等,则i往前推,让i等于next[i-1]
                while (i > 0 && chars[j] != chars[i]){
                    i = next[i-1];
                }
                //注意这里:需要判断是因为两值相等结束循环的还是i==0,如果此时chars[i] == chars[j],则说明还是有相等前后缀的。
                if (chars[i] == chars[j]){
                    i++;
                }
                next[j] = i;
                j++;
            }
        }
        return next;
    }
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值