字符串查找之 KMP 算法思路讲解和代码实现

算法介绍

KMP 算法是一种改进的字符串匹配算法,由 D.E.Knuth,J.H.Morris 和 V.R.Pratt 提出的。KMP 算法的核心是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的,KMP 算法的时间复杂度 O(m+n)。

相同前后缀

KMP 算法之所以能够快速匹配,主要是因为在迭代过程中并不是逐个查找,而是根据模式串的最长相同前后缀表进行跳跃查找。这里的相同前后缀指的是字符串中前缀和后缀相同的子串,不包括整个字符串

示例 1:“aba”,“a”既是“aba”的前缀,也是“aba”的后缀,所以“a”就是“aba”的相同前后缀。
示例 2:“ababa”,“a”是“ababa”的相同前后缀,“aba”也是“ababa”的相同前后缀。

下面演示“abacabad”的最长相同前后缀表是如何得到的。

i
0
1
2
3
4
5
6
7
子串
a
ab
aba
abac
abaca
abacab
abacaba
abacabad
最长相同前后缀
-
-
a
-
a
ab
aba
-
table
0
0
1
0
1
2
3
0

查找演示

看看 KMP 算法如何使用最长相同前后缀表进行查找。

字符串,“abacabaabacabad”
模式串,“abacabad”

匹配图解
i01234567891011121314
字符串a✅b✅a✅c✅a✅b✅a✅a❎bacabad
模式串a✅b✅a✅c✅a✅b✅a✅d❎
j01234567
最长前后缀表00101230

字符串和模式串前 7 个字符都能匹配上,当 i=7,j=7 时,匹配失败。如果是暴力查找,那么就需要让 i=1,j=0,然后重新开始匹配,如下:

i01234567891011121314
字符串ab❓acabaabacabad
模式串a❓bacabad
j01234567

如果是 KMP 算法会是怎样呢?
直接将模式串已成功匹配的前缀“aba”和字符串已成功匹配的“aba”对齐,让 i=7,j=3,然后继续匹配。如下:

i01234567891011121314
字符串abaca✅b✅a✅a❓bacabad
模式串a✅b✅a✅c❓abad
j01234567
最长前后缀表00101230

因为只有模式串成功匹配的子串“abacaba”中的某个前缀和字符串中已经匹配成功的子串“abacaba”中的某个后缀相同,后续的比较才有意义,而且只有取最长的相同前后缀才不会遗漏可能的匹配,而“aba”就是“abacaba”的最长相同前后缀。

i=7,j=3 匹配失败,所以继续将模式串已成功匹配的前缀“a”和字符串已成功匹配的“a”对齐,让 i=7,j=1,然后继续匹配。如下:

i01234567891011121314
字符串abacaba✅a❓bacabad
模式串a✅b❓acabad
j01234567
最长前后缀表00101230

i=7,j=1 匹配失败,因为已成功匹配的子串没有相同前后缀,所以将模式串整体后移和字符串未匹配成功的字符对齐,让 i=7,j=0,然后继续匹配下去。如下:

i01234567891011121314
字符串abacabaa✅b✅a✅c✅a✅b✅a✅d✅
模式串a✅b✅a✅c✅a✅b✅a✅d✅
j01234567
最长前后缀表00101230

最终匹配完成,从第一第二阶段来看,KMP 算法比暴力查找算法少了很多无用的循环,所以效率比较比暴力查找自然快多了。注:KMP 算法时间复杂度是 O(m+n),暴力查找是 O(m*n)。

算法实现

public int strStr(String text, String pattern) {
    if ("".equals(pattern)) {
        return 0;
    }
    int[] table = new int[pattern.length()];
    int i = 0, j = 1;
    while (j < table.length) {
        if (pattern.charAt(i) == pattern.charAt(j)) {
            table[j++] = i + 1;
            i++;
        } else {
            if (i == 0) {
                j++;
            } else {
                i = table[i - 1];
            }
        }
    }

    i = 0;
    j = 0;
    while (i < text.length()) {
        if (text.charAt(i) == pattern.charAt(j)) {
            i++;
            j++;
        } else {
            if (j == 0) {
                i++;
            } else {
                j = table[j - 1];
            }
        }
        if (j == pattern.length()) {
            return i - j;
        }
    }
    return -1;
}

视频讲解

  1. 在 YouTube 上查看请点击https://youtu.be/kUaiHxkX7lY
  2. 在 B 站上查看请点击https://www.bilibili.com/video/BV1bi4y1x7As
  3. 在西瓜视频上查看请点击https://www.ixigua.com/i6840806957645824516
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值