对KMP算法的一点碎碎念——上篇

news2024/11/28 8:49:37

对KMP算法的一点碎碎念——上篇

文章目录

  • 对KMP算法的一点碎碎念——上篇
    • 1. KMP 算法 Next数组 求解问题
      • 1.1 前置知识-最长公共前后缀LCP
        • 1.1.1 前缀与后缀
        • 1.1.2 最长公共前后缀LCP
      • 1.2 手算法求解 Next数组值(3种常见情况)
        • 1.2.1 情况1: next数组 正常存放匹配字符的长度
          • 情况1的失配回溯机制
        • 1.2.2 情况2: next数组 整体右移一位
          • 情况2的失配回溯机制
        • 1.2.3 情况3: next数组 整体右移一位并把next数组加1
          • 情况3的失配回溯机制
    • 参考资料

1. KMP 算法 Next数组 求解问题

假设有模式串T为:a b a b a c,求解与其对应的next数组为多少

1.1 前置知识-最长公共前后缀LCP

1.1.1 前缀与后缀

前缀的概念:前缀是 不包含最后一个字符 的所有 以第一个字符开头 的任意子串

后缀的概念:后缀是 不包含第一个字符 的所有 以最后一个字符结尾 的任意子串

例如字符串 “aba”

  1. 去掉最后一个字符后,剩下的都是前缀了

    a b a ab\xcancel{a} aba ,这里 ab 就是这个字符串的其中一个前缀

  2. 同理去掉第一个字符后,剩下的都是后缀了

    a b a \xcancel{a}ba a ba,这里 ba 就是这个字符串的其中一个后缀

为什么这里我说是其中一个前/后缀呢?

回到前后缀的概念上,前后缀都是以子串的形式存在的,也就是说,前后缀一定是模式串的子集

那么就好理解了,aba的前后缀表如下:

前缀后缀
aa
abba

1.1.2 最长公共前后缀LCP

概念:最长公共前后缀 (longest common prefix) 就是字符串中前缀和后缀的 最长匹配子串

例如,“aabaa”,我们从 前缀(prefix)和后缀(suffix) 中寻找最长的匹配子串

字符串 aabaa 的子串前缀(去掉最后一个字符)被去掉的字符后缀(去掉第一个字符)被去掉的字符前后缀最长匹配数(就是next值)
a a \xcancel{a} a a \xcancel{a} a 0
aaa a a a\xcancel{a} aa a a a \xcancel{a}a a a1
aaba, aa a a b aa\xcancel{b} aab b, ab a a b \xcancel{a}ab a ab0
aabaa, aa, aab a a b a aab\xcancel{a} aaba a, ba, aba a a b a \xcancel{a}aba a aba1
aabaaa, aa, aab, aaba a a b a a aaba\xcancel{a} aabaa a, aa, baa, abaa a a b a a \xcancel{a}abaa a abaa2

1.2 手算法求解 Next数组值(3种常见情况)

由于KMP算法中的next数组有不同的实现方式,因此为了避免大家弄混淆,我对每个实现next数组的方法做一些区分

1.2.1 情况1: next数组 正常存放匹配字符的长度

这是最常见的情况,基本上网络上大部分都是以这个情况为主来求解next数组值,我们上面也讨论过了next值如何得出

以模式串 “ababac” 为例,完整的next数组如下:

模式串下标012345
模式串ababac
next数组值001230
匹配的前后缀a b a
匹配位为前后缀 ‘a’
a b a b
匹配位为前后缀 ‘a b’
a b a b a
匹配位为前后缀 ‘a b a’

我们可以发现,每个字符下的next数组值都是存放着当前串的匹配长度

初学者可能会对第4个位置有疑惑,咱们一起来看如何求解?

模式串匹配到第4个字符后,前4个字符组成了一个串,即"ababa"

  • 前缀的集合为: a , a b , a b a , a b a b a,ab,aba,abab a,ab,aba,abab

  • 后缀的集合为: a , b a , a b a , b a b a a,ba,aba,baba a,ba,aba,baba

通过观察,我们可以看到集合中 a b a aba aba 为最长公共前后缀,且长度为3

情况1的失配回溯机制

假如文本串(主串)为 “abababac”,模式串为 “ababac”,在下标为5的位置发生失配

从图中我们看出:

  1. 左侧图,当主串S和模式串T比较到下标为5的位置时,发现主串和模式串不匹配,故模式串的指针j需要回退,回退的顺序为

    1. 寻找找从当前失配位置的前一位,它的next值是多少?

      当前失配位置为下标5,它前一位的next值为3

    2. 前一位的next值就是j要回退的位置的下标

      那么j要回退的位置就是 j = next[j-1] = next[4] = 3

  2. 右侧图,我们已经找到回退的位置了,故j回到下标为3的位置上继续与主串S重新匹配

还有一种的实现方式是和这个原理一样的,就是把这所有的next数组值减1,然后找回溯位置时再把next值加1而已

模式串下标012345
模式串ababac
next数组值-1-1012-1
回溯位:j = next[j-1] + 1

不难看出,虽然好理解,但是操作很繁琐。每一次j失配都需要找前一位的next值作为自己的回退位置,这时候有人对next数组做出了改进,当在当前位置失配时,直接获取当前失配位置的next值作为j回退的位置,这就是我们要讲解的下一种情况


1.2.2 情况2: next数组 整体右移一位

以模式串 “ababac” 为例,完整的next数组如下:

模式串下标012345
模式串ababac
next数组值-100123
匹配的前后缀a b a
匹配位为前后缀 ‘a’
a b a b
匹配位为前后缀 ‘a b’
a b a b a
匹配位为前后缀 ‘a b a’

你可能会疑惑,这样做也没什么区别啊,反而更难理解了?实则不然,我们看下面的比较方式就能看出来了

字符串匹配最本质的原理其实就是前后缀相匹配的问题,我们把模式串右移一位,在逻辑上更符合匹配的情况,这就是为什么大部分教程和书籍都用这两种方式讲解next数组值的原因。那么,除了逻辑上更符合之外,还有next数组右移一位还有什么优势呢?我们再看下面的图解

情况2的失配回溯机制

假如文本串(主串)为 “abababac”,模式串为 “ababac”,使用右移模式串T的方式与主串S进行匹配

当前位置不匹配,那么就直接从不匹配的位置获取next数组值,然后j就回退到当前位置的next对应的下标位置。对齐的那个地方不算一个步骤,只是为了让大家更好理解

通过以上图片对比,我们发现把next数组整体右移一位在一定情况下的匹配效率更高,这就是为什么右移next数组这么流行的原因了

回溯位:j = next[j]

1.2.3 情况3: next数组 整体右移一位并把next数组加1

以模式串 “ababac” 为例,完整的next数组如下:

模式串下标0123456
模式串ababac
next数组值011234
匹配的前后缀a b a
匹配位为前后缀 ‘a’
a b a b
匹配位为前后缀 ‘a b’
a b a b a
匹配位为前后缀 ‘a b a’

其实情况3的实现方式和情况2是一样的,只不过我们发现情况3的next数组的初始位置是从1开始,而情况1的next数组的初始位置是从0开始的

不过我个人认为,情况3更像是情况1和情况2的结合,它杂糅了它们的思想,为什么这么说?先给出结论

  1. 在回溯机制上,情况3的回溯机制思想和情况2的回溯机制思想是一样的

    都是当前位置不匹配,那么就直接从不匹配的位置获取next数组值,然后j就回退到当前位置的next对应的下标位置

    情况3的回溯机制也就是 j = next[j]
    而不是情况1的 j = next[j-1]
    
  2. 在next数组值确定上,情况3的数组值确定方式和情况1是一样的

    都是从当前位置及之前构成的串中寻找 最长公共前后缀,然后把匹配的值确定为当前位置的next值

情况3的失配回溯机制

假如文本串(主串)为 “abababac”,模式串为 “ababac”,使用右移模式串T并把下标加1的方式与主串S进行匹配

回溯位:j = next[j]

参考资料

KMP算法之求解next数组 (xiaohongshu.com)

帮你把 KMP 算法学个通透!(理论篇)

帮你把KMP算法学个通透!(求next数组代码篇)

KMP 算法之求next数组代码讲解

KMP算法精讲(1)——暴力匹配算法

KMP算法精讲(2)——什么是最长公共前后缀?

KMP算法精讲(3)——最长公共前后缀在KMP算法中的应用

KMP算法精讲(4)——15分钟搞定next数组

KMP Algorithm for Pattern Searching - GeeksforGeeks

Prefix function - Knuth-Morris-Pratt Algorithm - Coding Ninjas

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/576919.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

前端面试-React专题

目录 一.React1. React 的主要特性是什么2.React 的核心思想是3. 什么是jsx4. render()函数5. 函数组件和类组件的区别6. HTML和React中事件处理的区别7. React 中的合成事件8. 虚拟Dom?为什么使用?如何实现?9. 在 constructor 中给 super 函…

Excel - 如何给单元格加上下拉框

当你使用下拉列表来限制人们在单元格中的输入时,数据输入会更快、更准确。当有人选择一个单元格时,下拉列表的向下箭头就会出现,可以点击它并进行选择。 创建一个下拉列表 / Create a drop-down list 你可以通过提供下拉列表使工作表更有效率…

基于微信小程序的教学质量评价系统

末尾获取源码 开发语言:Java Java开发工具:JDK1.8 后端框架:SSM 前端:Vue 数据库:MySQL5.7和Navicat管理工具结合 服务器:Tomcat8.5 开发软件:IDEA / Eclipse 是否Maven项目:是 目录…

ubuntu下编译esp32 micropython固件编译(可自行增加模块)

目录 0. 前言1. 安装ESP-IDF2. 初始化Micropython仓库3. 选择ESP-IDF相应版本情况1情况2 4. 开始编译5.烧录固件 0. 前言 为ESP32编译Micropython固件 操作系统:ubuntu22.04 1. 安装ESP-IDF 本节需要ESP-IDF环境,安装开发环境是必要的。 …

FPGA之手把手教你做多路信号发生器(STM32与FPGA数据互传控制波形生成)

文章目录 博主的念叨一、任务介绍1、本文目标2、设计思路3、设计注意事项 二、设计代码1.顶层文件代码2.波形生成模块3.ROM例化4.PLL例化5.引脚分配 总结 博主的念叨 博主建了一个技术资源分享的群,开源免费,欢迎进来唠嗑280730348 最近趁热打铁做了一…

pandas库的常用操作介绍

目录 1.1.Pandas概述2.Pandas索引结构3.groupby学习5.Pandas数值运算二元统计6.对象操作7.merge合并显示设置9.pivot操作10. 时间操作11.常用操作12.groupby操作13.字符串操作14.索引操作15.pandas绘图操作 1.1.Pandas概述 Python的pandas库是一个数据处理和数据分析库&#x…

javascript基础七:说说你对Javascript中作用域的理解?

一、作用域 作用域,即变量(变量作用域又称上下文)和函数生效(能被访问)的区域或集合 换句话说,作用域决定了代码区块中变量和其他资源的可见性 举个粟子 function myFunction(){let name小爱同学 } undef…

6.2:荷兰国旗问题

文章目录 实现key前面的数都小于等key,key后面的数都大于等于key1:前后指针法:2:挖坑法3:单指针法(左神) 辗转相除法求最大公约数 实现key前面的数都小于等key,key后面的数都大于等于…

【leetCode:剑指 Offer】20. 表示数值的字符串

1.题目描述 请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。 数值(按顺序)可以分成以下几个部分: 若干空格 一个 小数 或者 整数 (可选)一个 e 或 E ,后面跟着一个 …

深入篇【C++】类与对象:详解内部类+匿名对象+编译器对拷贝的优化

这里写目录标题 Ⅰ.内部类【特点】1.天生友元2.直接访问static成员3.访问限制符限制4.外部类的大小 Ⅱ.匿名对象【特点】1.一行生命域2.对象具有常性3.可强行续命 Ⅲ.拷贝对象时编译器的优化 Ⅰ.内部类 概念:一个类定义在另一个类内部,这个内部的类就叫做…

【Unity100个实用小技巧】如何修改UI上材质的Shader

☀️博客主页:CSDN博客主页💨本文由 萌萌的小木屋 原创,首发于 CSDN💢🔥学习专栏推荐:面试汇总❗️游戏框架专栏推荐:游戏实用框架专栏⛅️点赞 👍 收藏 ⭐留言 📝&#…

mysql基本操作1

库的基本操作 1.show variables like character_set_database 查看系统默认的字符集,若是指定数据库下使用该SQL,则查看的是该数据库对应的字符集。 2.show variables like collation_database 查看系统默认的字符集校验规则,指定数据库下使用…

Java 集合 - List 接口

文章目录 1.List 接口介绍2.List 接口常用 API3.ListIterator 迭代器4.ArrayList - 动态数组4.1 ArrayList 概述4.2 手撸动态数组 5.Vector - 动态数组6.LinkedList - 双向链表6.1 链表概述6.2 手撸双链表6.3 链表与动态数组的区别 7.Stack - 栈8.总结 1.List 接口介绍 在 Jav…

Linux:查看主机运行状态的一系列命令:top、df、iostat、sar

Linux:查看主机运行状态的一系列命令:top、df、iostat、sar 命令top监控系统资源: 使用top(回车)命令后,整个控制台会变成任务管理器的形式: 退出可以使用:ctrlc 或 q 第一行补充:表示正在执行的…

高程复习 欧几里得算法和扩展欧几里得算法考试前冲刺简约版

gcd(m,n)gcd(n,m%n) gcd欧几里得算法标准代码求最大公约数 #include <iostream>using namespace std;typedef long long LL; LL gcd(int a,int b) {if(b0)return a;return gcd(b,a%b); } int main() {LL a,b;cin>>a>>b;cout<<gcd(a,b)<<endl;re…

Linux基础:文件权限详细说明(全)

一、前提 我们要知道&#xff0c;Linux系统&#xff0c;一切皆文件的含义。 对于Linux来说&#xff0c;一切皆文件。 我们常涉及到的概念是目录和文件。 权限主要有三种&#xff1a;r(读)w(写)x(执行)。 二、正文 1、修改文件或者目录所属用户和所属组 chown [用户名[:组名…

规则网络构建

规则网络构建 文章目录 规则网络构建[toc]1 规则网络定义2 规则网络的构建3 代码实现 1 规则网络定义 常见规则网络包包括全局耦合网络、最近邻耦合网络和星型耦合网络&#xff0c;三种规则网络定义如下&#xff1a; (1)全局耦合网络&#xff1a;任意两个节点均存在连边的网络…

云原生 HTAP -- PolarDB-IMCI:A Cloud-Native HATP Database

文章目录 0 背景1 IMCI 架构1.1 架构演进的背景1.2 基本架构1.2 基本使用1.4 列索引存储 设计1.5 RW-RO 的数据同步实现1.5.1 CALS1.5.2 2P-COFFER 1.6 计算引擎实现1.7 性能 近期除了本职工作之外想要再跟进一下业界做讨论以及落地的事情&#xff0c;扩宽一下视野&#xff0c;…

算法7.从暴力递归到动态规划0

算法|7.从暴力递归到动态规划0 1.汉诺塔 题意&#xff1a;打印n层汉诺塔从最左边移动到最右边的全部过程 解题思路&#xff1a; 把字母抛掉&#xff0c;变成左中右三个盘子多个盘子能一下到吗&#xff1f;不能&#xff0c;把上边的拿走&#xff0c;最下边的才能放到指位置(…

java汉字转拼音pinyin4j-2.5.0.jar用法

要先下载哦&#xff0c; pinyin4j下载链接 可能会出现Cannot resolve symbol ‘net’&#xff0c;找到上面文件的下载路径&#xff0c;IDEA中File->Project Structure -> Modules->Dependencies import java.util.*; import net.sourceforge.pinyin4j.PinyinHelper;…