【恋上数据结构】串匹配算法(蛮力匹配、KMP【重点】、Boyer-Moore、Karp-Rabin

本文介绍了字符串匹配中的蛮力算法和KMP算法,包括它们的执行过程、优化策略以及性能分析。重点讲述了KMP算法中的next表构造及其在Java开发中的应用。
摘要由CSDN通过智能技术生成

本课程研究的是开发中非常熟悉的字符串,是由若干个字符组成的有限序列

在这里插入图片描述

字符串 thank前缀(prefix)、真前缀(proper prefix)、后缀(suffix)、真后缀(proper suffix)

在这里插入图片描述

串匹配算法

========================================================================

  • 查找一个模式串(pattern)在文本串(text)中的位置:

String text = “Hello World”;

String pattern = “or”;

text.indexOf(pattern); // 7

text.indexOf(pattern); // -1

几个经典的串匹配算法:

  • 蛮力(Brute Force)

  • KMP

  • Boyer-Moore

  • Karp-Rabin / Rabin-Karp

  • Sunday

下面用 tlen 代表文本串 text 的长度,plen 代表模式串 pattern 的长度;

蛮力(Brute Force)

==================================================================================

  • 以字符为单位,从左到右移动模式串,直到匹配成功 ;

在这里插入图片描述

蛮力算法有 2 种常见实现思路:

蛮力1 – 执行过程 + 实现


在这里插入图片描述

在这里插入图片描述

/**

  • 蛮力匹配

*/

public static int indexOf(String text, String pattern) {

if (text == null || pattern == null) return -1;

char[] textChars = text.toCharArray();

int tlen = textChars.length;

char[] patternChars = pattern.toCharArray();

int plen = patternChars.length;

if (tlen == 0 || plen == 0 || tlen < plen) return -1;

int pi = 0, ti = 0;

while (pi < plen && ti < tlen) {

if (textChars[ti] == patternChars[pi]) {

ti++;

pi++;

} else {

ti = ti - pi + 1;

// ti -= pi - 1;

pi = 0;

}

}

return pi == plen ? ti - pi : -1;

}

蛮力1 – 优化


在这里插入图片描述

/**

  • 蛮力匹配 - 改进

*/

public static int indexOf(String text, String pattern) {

if (text == null || pattern == null) return -1;

char[] textChars = text.toCharArray();

int tlen = textChars.length;

char[] patternChars = pattern.toCharArray();

int plen = patternChars.length;

if (tlen == 0 || plen == 0 || tlen < plen) return -1;

int pi = 0, ti = 0;

while (pi < plen && ti - pi <= tlen - plen) { // ti - pi <= tlen - plen 是关键

if (textChars[ti] == patternChars[pi]) {

ti++;

pi++;

} else {

ti = ti - pi + 1;

// ti -= pi - 1;

pi = 0;

}

}

return pi == plen ? ti - pi : -1;

}

蛮力2 – 执行过程 + 实现


在这里插入图片描述

在这里插入图片描述

/**

  • 蛮力匹配2

*/

public static int indexOf(String text, String pattern) {

if (text == null || pattern == null) return -1;

char[] textChars = text.toCharArray();

int tlen = textChars.length;

char[] patternChars = pattern.toCharArray();

int plen = patternChars.length;

if (tlen == 0 || plen == 0 || tlen < plen) return -1;

// 如果模式串的头在 tlen - plen 后面, 必然会匹配失败

int tiMax = tlen - plen;

for (int ti = 0; ti <= tiMax; ti++) {

int pi = 0;

for (; pi < plen; pi++) {

if (textChars[ti + pi] != patternChars[pi]) break;

}

if (pi == plen) return ti;

}

return -1;

}

蛮力 – 性能分析


在这里插入图片描述

最好情况

  • 只需一轮比较就完全匹配成功,比较 m 次( m 是模式串的长度)

  • 时间复杂度为 O(m)

在这里插入图片描述

最坏情况(字符集越大,出现概率越低):

  • 执行了 n – m + 1 轮比较( n 是文本串的长度)

  • 每轮都比较至模式串的末字符后失败( m – 1 次成功,1 次失败)

  • 时间复杂度为 O(m ∗ (n − m + 1)),由于一般 m 远小于 n,所以为 O(mn)

在这里插入图片描述

KMP

======================================================================

KMP 是 Knuth–Morris–Pratt 的简称(取名自3位发明人的名字),于1977年发布

在这里插入图片描述

蛮力 vs KMP


首先大概了解一下两者的差距:

蛮力算法:会经历很多次没有必要的比较。

在这里插入图片描述

KMP算法:充分利用了此前比较过的内容,可以很聪明地跳过一些不必要的比较位置。

在这里插入图片描述

KMP – next表的使用


KMP 会预先根据模式串的内容生成一张 next 表(一般是个数组):

在这里插入图片描述

例如,下图串匹配时, pi = 7 时 失配

  • 根据失配的索引 7 查表,查到的元素索引为 3

next[pi] == 3

  • 将模式串索引为 3 的元素移动到失配的位置

pi = next[pi]; // pi = 3

  • 向右移动的距离为 p - next[pi]

在这里插入图片描述

再比如:pi = 3 时失配, next[3] = 0,将 0 位置的元素移到失配处:pi = next[pi]

在这里插入图片描述

KMP – 核心原理(构造next表)


在这里插入图片描述

d、e 失配时,如果希望 pattern 能够一次性向右移动一大段距离,然后直接比较 d、c 字符

  • 前提条件是 A 必须等于 B

所以 KMP 必须在失配字符 e 左边的子串中找出符合条件的 A、B,从而得知向右移动的距离

向右移动的距离:e左边子串的长度 – A的长度,等价于:e的索引 – c的索引

c的索引 == next[e的索引],所以向右移动的距离:e的索引 – next[e的索引]

总结:

  • 如果在 pi 位置失配,向右移动的距离是 pi – next[pi],所以 next[pi] 越小,移动距离越大

  • next[pi] 是 pi 左边子串的真前缀后缀的最大公共子串长度

真前缀后缀的最大公共子串长度

如何求 真前缀后缀的最大公共子串长度

在这里插入图片描述

构造 next 表

根据最大公共子串长度得到 next 表:

在这里插入图片描述

-1的精妙之处

为什么要将首字符设置为 - 1?

在这里插入图片描述

KMP – 主算法代码实现


在这里插入图片描述

KMP – 为什么是“最大“公共子串长度?


假设文本串AAAAABCDEF模式串AAAAB

在这里插入图片描述

在这里插入图片描述

KMP – next表的构造思路及实现


在这里插入图片描述

已知 next[i] == n

① 如果 pattern.charAt(i) == pattern.charAt(n)

  • 那么 next[i + 1] == n + 1

② 如果 pattern.charAt(i) != pattern.charAt(n)

  • 已知 next[n] == k

  • 如果 pattern.charAt(i) == pattern.charAt(k)

那么 next[i + 1] == k + 1

  • 如果 pattern.charAt(i) != pattern.charAt(k)

将 k 代入 n ,重复执行 ②

构造 next 表 代码实现:

private static int[] next(String pattern) {

char[] chars = pattern.toCharArray();

int[] next = new int [chars.length];

next[0] = -1;

int i = 0;

int n = -1;

int iMax = chars.length - 1;

while (i < iMax) {

if (n < 0 || chars[i] == chars[n]) {

next[++i] = ++n;

} else {

n = next[n];

}

}

return next;

}

KMP – next表的不足之处


假设文本串是 AAABAAAAB,模式串是 AAAAB

在这里插入图片描述

在这里插入图片描述

KMP – next表的优化思路及实现


在这里插入图片描述

  • 如果 pattern[i] != d,就让模式串滑动到 next[i](也就是n)位置跟 d 进行比较;

  • 如果 pattern[n] != d,就让模式串滑动到 next[n](也就是k)位置跟 d 进行比较;

  • 如果 pattern[i] == pattern[n],那么当 i 位置失配时,

模式串最终必然会滑到 k 位置跟 d 进行比较,

所以 next[i] 直接存储 next[n](也就是k)即可;

next 表 的优化代码实现:

private static int[] next(String pattern) {

char[] chars = pattern.toCharArray();

int[] next = new int [chars.length];

next[0] = -1;

int i = 0;

int n = -1;

int iMax = chars.length - 1;

while (i < iMax) {

if (n < 0 || chars[i] == chars[n]) {

// 优化

++i;

++n;

if (chars[i] == chars[n]) {

next[i] = next[n];

} else {

next[i] = n;

}

} else {

n = next[n];

}

}

return next;

}

KMP – next表的优化效果


在这里插入图片描述

KMP – 性能分析


KMP 主逻辑:

  • 最好时间复杂度:O(m)

  • 最坏时间复杂度:O(n),不超过O(2n)

next 表的构造过程跟 KMP 主体逻辑类似:

  • 时间复杂度:O(m)

KMP 整体:

  • 最好时间复杂度:O(m)

  • 最坏时间复杂度:O(n + m)

  • 空间复杂度: O(m)

在这里插入图片描述

KMP完整源码


public class KMP {

public static int indexOf(String text, String pattern) {

// 检测数据合法性

if (text == null || pattern == null) return -1;

char[] textChars = text.toCharArray();

int tlen = textChars.length;

char[] patternChars = pattern.toCharArray();

int plen = patternChars.length;

if (tlen == 0 || plen == 0 || tlen < plen) return -1;

// next表

int[] next = next(pattern);

int pi = 0, ti = 0;

while (pi < plen && ti < tlen) {

// next表置-1的精妙之处, pi = -1 则 pi = 0, ti++ 相当于模式串后一一位

if (pi < 0 || textChars[ti] == patternChars[pi]) {

ti++;

pi++;

} else {

pi = next[pi];

}
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

最后

经过日积月累, 以下是小编归纳整理的深入了解Java虚拟机文档,希望可以帮助大家过关斩将顺利通过面试。
由于整个文档比较全面,内容比较多,篇幅不允许,下面以截图方式展示 。







由于篇幅限制,文档的详解资料太全面,细节内容太多,所以只把部分知识点截图出来粗略的介绍,每个小节点里面都有更细化的内容!
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
会持续更新!**

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

最后

经过日积月累, 以下是小编归纳整理的深入了解Java虚拟机文档,希望可以帮助大家过关斩将顺利通过面试。
由于整个文档比较全面,内容比较多,篇幅不允许,下面以截图方式展示 。

[外链图片转存中…(img-f11Q37jE-1713420104346)]
[外链图片转存中…(img-g49Xg3Vt-1713420104346)]
[外链图片转存中…(img-duXn8HRi-1713420104347)]
[外链图片转存中…(img-KcO8WQUx-1713420104347)]
[外链图片转存中…(img-EJjd7mk6-1713420104347)]
[外链图片转存中…(img-2vbNt1Vi-1713420104347)]
[外链图片转存中…(img-YrvhCQsP-1713420104347)]

由于篇幅限制,文档的详解资料太全面,细节内容太多,所以只把部分知识点截图出来粗略的介绍,每个小节点里面都有更细化的内容!
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值