KMP算法详解

KMP算法详解

作者:木子六日;

日期:2021年3月8日;

介绍

问题引入

假设有一个字符串a和字符串b;

若b是a子串,返回b在a中的位置,如a="xxyyzz",b="zz",则返回4;

若b不是a的子串,则返回-1;

暴力解法

暴力显然是可以做的,我们判断a每一个位置作为开头能否匹配出b即可,时间复杂度就是a.length*b.length

KMP算法其实也是这个思路,只不过回退的策略发生改变,不再是每次都傻傻的回退到最前面;

next数组的介绍

next数组是用来帮助KMP实现最省事的回退。

他的概念是这样的(这里先说下概念,求法后面有):

next数组的长度和b相同,next[i]表示b[i]前面的所有字符的最长的相同前后缀(不包含前面的整个字符串);

举个例子,b="aacaacaade",那么next[8]就是字符d前面字符串"aacaacaa",他的相等的前后缀有"a","aa","aacaa",但是最长的是5位,所以next[8]=5。

我们规定next[0]=-1,next[1]=0。

KMP算法流程

过程

我们不再回退a了,只回退b,而且回退的位置由next数组决定。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eNeORE6t-1615188466608)(C:\Users\15872\AppData\Roaming\Typora\typora-user-images\1615186872537.png)]

我们一开始都是从字符串1和2的0位置开始匹配,发现fg不相等了,按照暴力的解法,这时候字符串1就需要跳回到1位置的b再由1位置作为起始位置和字符串2做匹配;

而KMP的做法是这样的:

  1. 字符串1不动;
  2. 将字符串2的位置跳回到next[i]的位置,i位置标示字符不匹配的位置;
  3. 如果还不匹配,接着跳,next[next[i]];
  4. 若字符串2跳回到0位置了,字符串1就要往后移一个了;

能够这样做完全是因为next表示了最长的相等前后缀,不明白的自己试几个例子,按照上面的流程走一遍就明白了。

代码

public static int indexOf(String a, String b) {
    if (a == null || b == null || a.length() < b.length() || b.length() < 1) {
        return -1;
    }
    //下文会给出next数组的求法
    int[] next = next(b);
    int i1 = 0, i2 = 0;
    while (i1 < a.length() && i2 < b.length()) {
        if (a.charAt(i1) == b.charAt(i2)) {
            i1++;
            i2++;
        } else if (i2 == 0) {
            i1++;
        } else {
            i2 = next[i2];
        }
    }
    return i2 == b.length() ? i1 - i2 : -1;
}

这里不考虑next的求解的话,时间复杂度就是由这个循环决定的。

我们来看下这个循环到底走了多少次:

来看这两个值,v1 = i1v2 = i1-i2,v1和v2的最大值显然都是n = a.length;

循环里面是一个分支结构:

  1. if分支:v1增加,v2不变;
  2. else if分支:v1增加,v2增加;
  3. else分支:v1不变,v2增加;

所以如果循环次数超过了2n次,v1或v2里面必定有一个会爆掉。

所以这个循环的时间复杂度是O(n).

如何求next数组

如果b[i] == b[next[i-1]]

next[i] = next[i-1] + 1

不然的话,next[i-1]就再往前跳即可,跟kmp类似;

代码如下:

private static int[] next(String b) {
    if (b.length() == 1) {
        return new int[] { -1 };
    }
    if (b.length() == 2) {
        return new int[] { -1, 0 };
    }
    int[] result = new int[b.length()];
    result[0] = -1;
    result[1] = 0;
    int i = 2, j = 0;
    while (i < b.length()) {
        if (b.charAt(i - 1) == b.charAt(j)) {
            result[i++] = ++j;
        } else if (j == 0) {
            i++;
        } else {
            j = result[j];
        }
    }
    return result;
}

可以看出这个while循环的次数不超过2*b.length次,设b.length = m,则时间复杂度为O(m).

综上所述,整个KMP算法的时间复杂度为O(m+n).

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值