kmp算法(最简单最直观的理解,看完包会)

2023-11-11

本文将以特殊的方式来让人们更好地理解kmp算法,不包括kmp算法的推导,接下来,我们将从朴素算法出发。
在这之前,我们先设主串为S,模式串为T,我们要解决的询问是主串中是否包含模式串(即T是否为S的子串)。
版权声明:本文为原创文章,转载请标明出处。

朴素算法

朴素算法说白了就是暴力,简单地讲就是先从主串的第一个位置开始逐个对模式串进行匹配,若匹配失败,则从主串的第二个位置继续进行匹配,以此类推,直到匹配成功或主串的结尾。
举个例子1
主串S:aabaaced
模式串T:aac
首先我们会进行这样的匹配
aabaaced
aac
发现T[0]和S[0]匹配,T[1]和S[1]匹配,而T[2]==c和S[2]==b匹配失败,接着我们会这样
aabaaced
  aac
发现T[1]和S[1]匹配,而T[2]==c和S[3]==b匹配失败,接着
aabaaced
    aac
发现T[2]和S[2]不匹配,继续
aabaaced
      aac
这次终于成功匹配。
以上所述就是朴素算法,然而我们再来看一个例子
举个例子2
主串S:aaaaaaaaaaaaaaaaaaaaab
模式串T:aaaaab
如果这个例子我们还用朴素算法去匹配,很显而易见,每次我们都要从头开始匹配,做法如下
aaaaaaaaaaaaaaaaaaaaab
aaaaab
从T[0]到T[5],对S[0]和S[5]依次进行匹配,发现末尾(T[5]和S[5])没有匹配,继续
aaaaaaaaaaaaaaaaaaaaab
  aaaaab
从T[0]到T[5],对S[1]和S[6]依次进行匹配,发现末尾(T[5]和S[6])没有匹配,继续
……(此处省略大量的中间过程)
aaaaaaaaaaaaaaaaaaaaab
                             aaaaab
终于匹配成功。
如果用kmp算法,则过程如下:
aaaaaaaaaaaaaaaaaaaaab
aaaaab
从T[0]到T[5],对S[0]和S[5]依次进行匹配,发现末尾(T[5]和S[5])没有匹配,继续
aaaaaaaaaaaaaaaaaaaaab
  aaaaab
直接匹配T[5]和S[6]发现匹配失败,继续
……(此处省略大量的中间过程)
aaaaaaaaaaaaaaaaaaaaab
                                  aaaaab
我们发现kmp算法从第二次匹配开始省略了T[0]到T[4]对S的匹配,因为由kmp算法我们知道T[0]到T[4]一定已经匹配了,不需要再判断,那么kmp算法是怎么知道并利用这些信息的呢,
接下来我们进入正题。

kmp算法的理解

首先我们从朴素算法出发,一步一步去引出kmp算法
主串S:S[1]S[2]S[3]S[4]S[5]S[6]S[7]S[8]S[9]
模式串T:T[1]T[2]T[3]T[4]T[5]T[6]
一开始,我们先用朴素算法进行匹配,得到
S[1]S[2]S[3]S[4]S[5]S[6]S[7]S[8]S[9]
T[1]T[2]T[3]T[4]T[5]T[6]
这时候,我们假设前四个匹配成功了,然而S[5]与T[5]匹配失败,即有
T[1]==S[1]
T[2]==S[2]
T[3]==S[3]
T[4]==S[4]
T[5]!=S[5]
按照朴素算法的做法,我们应该把T串往右移,得到这样的式子进行匹配
S[1]S[2]S[3]S[4]S[5]S[6]S[7]S[8]S[9]
      T[1]T[2]T[3]T[4]T[5]T[6]
但是这时候我们思考这样一个问题,将模式串右移一位是否有可能成功匹配??
显而易见,这样匹配成功的充要条件是:
T[1]==S[2]
T[2]==S[3]
T[3]==S[4]
T[4]==S[5]
T[5]==S[6]
T[6]==S[7]
结合上次匹配的结果,我们可以把这次匹配成功的充要条件进行变化:
T[1]==S[2]==T[2]
T[2]==S[3]==T[3]
T[3]==S[4]==T[4]
T[4]==S[5]
T[5]==S[6]
T[6]==S[7]
由此我们可以得出一个上次匹配失败后将模式串T右移一位能够匹配成功的充要条件:
T[1]==T[2]
T[2]==T[3]
T[3]==T[4]
T[4]==S[5]
T[5]==S[6]
T[6]==S[7]
进而得到上次匹配失败后将模式串T右移一位能够过匹配成功的必要条件:
T[1]==T[2]
T[2]==T[3]
T[3]==T[4]
注意,这个必要条件只和模式串T有关!
接着我们讨论将模式串右移两位是否能匹配成功:
S[1]S[2]S[3]S[4]S[5]S[6]S[7]S[8]S[9]
             T[1]T[2]T[3]T[4]T[5]T[6]
显而易见,这样匹配成功的充要条件是:
T[1]==S[3]
T[2]==S[4]
T[3]==S[5]
T[4]==S[6]
T[5]==S[7]
T[6]==S[8]
结合上次匹配的结果,我们可以把这次匹配成功的充要条件进行变化:
T[1]==S[3]==T[3]
T[2]==S[4]==T[4]
T[3]==S[5]
T[4]==S[6]
T[5]==S[7]
T[6]==S[8]
进而得到上次匹配失败后将模式串T右移两位能够过匹配成功的必要条件:
T[1]==T[3]
T[2]==T[4]
注意,这个必要条件只和模式串T有关!
最后我们讨论将模式串右移三位是否能匹配成功:
S[1]S[2]S[3]S[4]S[5]S[6]S[7]S[8]S[9]
                   T[1]T[2]T[3]T[4]T[5]T[6]
显而易见,这样匹配成功的充要条件是:
T[1]==S[4]
T[2]==S[5]
T[3]==S[6]
T[4]==S[7]
T[5]==S[8]
T[6]==S[9]
结合上次匹配的结果,我们可以把这次匹配成功的充要条件进行变化:
T[1]==S[4]==T[4]
T[2]==S[5]
T[3]==S[6]
T[4]==S[7]
T[5]==S[8]
T[6]==S[9]
进而得到上次匹配失败后将模式串T右移三位能够过匹配成功的必要条件:
T[1]==T[4]
上面讨论了三种情况,在第一次匹配到T[5]的时候匹配失败了,将模式串分别右移动一位,右移动两位,右移动三位
是否有可能成功
我们这里设Q为T[1]T[2]T[3]T[4]
可以发现:
右移动一位成功的必要条件是T[1]==T[2],T[2]==T[3],T[3]==T[4],即Q的三个前缀等于三个后缀(T[1]T[2]T[3]==T[2]T[3]T[4])

右移动两位成功的必要条件是T[1]==T[3],T[2]==T[4],即Q的两个前缀等于两个后缀!(T[1]T[2]==T[3]T[4])

右移动三位成功的必要条件是T[1]==T[4],即Q的一个前缀等于一个后缀!
注意,这些移动都只和模式串有关!
这时候,我们可以得出一个结论:
上面这个例子,T[5]是匹配失败的位置,我们把匹配失败的位置的前面的所有字符看作一个新的串Q,想要知道右移几位有可能匹配成功,我们需要讨论T[5]前面的字符组成的串Q,如果不满足Q的三个前缀等于三个后缀,我们可以直接跳过右移一位的情况,如果不满足Q的两个前缀等于两个后缀,我们可以直接跳过右移两位的情况,等等,而且,如果一旦满足,我们在右移后,不需要从模式串的头部开始匹配,因为如果满足,前面几个就已经匹配好了。就比如上面这个例子,若满足:
T[1]==T[2]
T[2]==T[3]
T[3]==T[4]
我们可以得到右移一位有可能匹配成功,而且因为有上次匹配失败后留下的信息
T[2]==S[2]
T[3]==S[3]
T[4]==S[4]
我们可以直接得到
T[1]==T[2]==S[2]
T[2]==T[3]==S[3]
T[3]==T[4]==S[4]
所以直接匹配T[4]和S[5]即可,这么一来,就是固定主串不动,从匹配失败的位置开始,判断模式串需要右移几位,然后从匹配失败的位置开始匹配即可,上面那个例子就是T[5]与S[5]匹配失败,由T[1]T[2]T[3]==T[2]T[3]T[4]可知接下来需要模式串右移一位并匹配T[4]和S[5]。

kmp算法的使用

在实际使用中,我们不可能匹配失败一次就去判断失败字符前面所有字符组成的串的最长相等的前缀和后缀,这样时间复杂度会很高,所以我们需要在匹配之前对模式串进行预处理,对每个字符如果匹配失败,要右移几位进行保存,在匹配中一旦失败,直接跳到那个位置就可以了,我们用next数组进行保存,比如上面的那个例子,T[5]匹配失败了,这时候就要让模式串的指针指向next[5],next[5]是我们在匹配之前就已经预处理过的。
至于如何处理,本文不给予证明,靠下面的几串代码可以实现,读者自行思考或阅读书籍或其它文章即可。
获得next数组的代码如下,T为模式串:

void get_next() {
    next[0] = -1;
    int i = 0, j = -1;
    int len = strlen(T);
    while(i < len) {
        if(j == -1 || T[i] == T[j])
            next[++i] = ++j;
         else
            j = next[j];
    }
}

代码很短,其中next[i]代表的是如果在i位置匹配失败,应该从哪个位置继续匹配,跟i前面所有字符组成的串Q的前缀与后缀有关。注意,这个next数组是kmp算法的核心。
接下来给出匹配的过程代码:

bool KMP() {
    get_next();
    int len1 = strlen(T);
    int len2 = strlen(S);
    int i = 0, j = 0;           //i指向模式串T,j指向主串S
    while(j < len2) {
        if(T[i] == S[j]) {
            i++;
            j++;
            if(i == len1) {
                return true;
            }
        } else {
            i = next[i];
            if(i == -1) {
                j++;i++;
            }
        }
    }
    return false;
}

kmp算法的练习建议

理解kmp算法:poj2752 poj2406 poj1961
常规kmp算法练习:poj3461 poj2185

如有错误或不妥之处,欢迎指正~

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

kmp算法(最简单最直观的理解,看完包会) 的相关文章

  • 4 x 3 锁图案

    我遇到了这个 它要求计算在 4x3 网格中可以制作特定长度的锁定图案的方式数 并遵循规则 可能有些点不能包含在路径中 有效的模式具有以下属性 图案可以使用第一次接触的点序列来表示 与绘制图案的顺序相同 从 1 1 到 2 2 的图案与图案不
  • 如何设置K-means openCV c++的初始中心

    我正在尝试使用 OpenCv 和 Kmeans 对图像进行分割 我刚刚实现的代码如下 include opencv2 objdetect objdetect hpp include opencv2 highgui highgui hpp i
  • 两个非嵌套循环的大 O 表示法

    对于两个非嵌套的 for 循环 大 O 表示法是什么 Example for int i 0 i
  • 如何动态查找连接组件

    使用不相交集数据结构可以很容易地得到图的连通分量 而且 它只是支持增量连接组件 http www boost org doc libs 1 46 1 libs graph doc incremental components html 然而
  • 如何从二叉搜索树中均匀随机地返回节点?

    给定一个 BST 可能平衡也可能不平衡 如何能够均匀地随机返回 任何 节点 一个限制是您不能使用外部索引数据结构 您必须以每个节点都有平等被访问的机会的方式遍历树 这个问题让我困惑了好一阵子 如果我们确实可以使用外部哈希表 指针 我们可以对
  • cordova 使用什么 js“引擎”?

    Cordova 使用什么 JS 引擎 它是特定于平台的还是跨所有平台的一个标准 意味着 iOS 的 safari 和 Android 的 chrome 以及 Windows 可能的 IE 标准 或者跨所有平台的 Cordova JS 引擎
  • Exposé 布局算法

    我正在制作一些项目 其布局类似于 Mac OS X 在 Expos 中对窗口所做的操作 它适应项目的长宽比和可用区域的长宽比 基本上 可用区域分为行和列 每个单元格 行和列的交集 中放置一个项目 这些项目必须保持其纵横比 此处width h
  • Codility 钉板

    尝试了解 Codility NailingPlanks 的解决方案 问题链接 https app codility com programmers lessons 14 binary search algorithm nailing pla
  • 如何将无向图转换为 DAG?

    The 维基页面 http en wikipedia org wiki Directed acyclic graph Relation to other kinds of graphs says 任何无向图都可以通过为其顶点选择总顺序并将每
  • 寻找距离原点最近的 100 颗恒星的算法

    首先让我提出正确的问题 问 有一个文件包含超过一百万个点 x y 每个点代表一颗星星 a b 处有一颗行星地球 现在 任务是构建一种算法 返回距离地球最近的 100 颗恒星 您的算法的时间和空间复杂度是多少 这个问题在各种采访中被问过很多次
  • 快速搜索压缩文本文件

    我需要能够在大量压缩文件 txt 中搜索文本 压缩可能会改变为其他东西 甚至成为专有的 我想避免解压所有文件并压缩 编码 搜索字符串并在压缩文件中搜索 这应该可以通过对所有文件使用相同的码本使用霍夫曼压缩来实现 我不想重新发明轮子 所以 任
  • 如何将一组重叠范围划分为不重叠范围?

    假设您有一组范围 0 100 一 0 75 b 95 150 c 120 130 d 显然 这些范围在某些点上重叠 您将如何剖析这些范围以生成不重叠范围的列表 同时保留与其原始范围相关的信息 在本例中为范围后面的字母 例如 运行算法后的上述
  • 点集子集的最小周长凸包

    给定平面上的 n 个点 没有 3 个共线 给定数字 k 找到 k 个点的子集 使得 k 个点的凸包在 k 个点的子集的任何凸包中具有最小周长 我可以想到一个简单的方法 运行时间为 O n k k log k 找到大小为 k 的每个子集的凸包
  • 如何求小于给定数的最大2次方

    我需要找到小于给定数字的最大 2 次幂 我陷入困境 找不到任何解决方案 Code public class MathPow public int largestPowerOf2 int n int res 2 while res lt n
  • 7 张牌扑克手牌评估器

    有谁知道评估 7 张牌扑克牌的快速算法吗 这比简单地暴力检查 7 张牌中每 21 个 5 张牌的组合更有效 Cheers Pete 我写了一篇JavaScript 核心评估方法仅使用位操作 因此速度非常快 考虑到这一点 查看 21 种组合还
  • 生成所有多集大小为 n 的分区的算法

    我一直在试图找出一种方法来生成多重集的所有不同的大小为 n 的分区 但到目前为止却空手而归 首先让我展示一下我想要实现的目标 假设我们有一个输入向量uint32 t std vector
  • 快速约会算法

    我在一家咨询公司工作 大部分时间都在客户所在地 正因为如此 我很少见到同事 为了更好地了解彼此 我们将安排一个晚宴 会有很多小桌子 方便人们聊天 为了在聚会期间与尽可能多的不同的人交谈 每个人都必须每隔一段时间 比如每小时 换一张桌子 如何
  • 在常数空间中创建 1..N 的随机排列

    我正在寻找枚举固定空间中数字 1 N 的随机排列 这意味着我无法将所有数字存储在列表中 原因是 N 可能非常大 超过可用内存 我仍然希望能够一次遍历这样一个数字的排列 只访问每个数字一次 我知道对于某些 N 可以这样做 许多随机数生成器随机
  • 如何有效地找到距给定点最远的点(从一组点中)?

    我正在寻找一种算法或数据结构来解决以下问题 给你一组点 S 然后你会得到另一个点形式的 Q 查询 对于每个查询 找到集合中距离给定点最远的点 集合中最多有 10 5 个点和 10 5 个查询 所有点的坐标都在 0 到 10 5 范围内 我想
  • 直接选择排序与交换选择排序

    有什么区别直接选择排序 vs 交换选择排序 今天我陷入了一场争论 我的教授在他的讲义中使用了这两个术语 维基百科和任何教科书或网站都会为您提供的选择排序就是他所说的 交换选择排序 我以前从未听说过 交换选择排序 这个术语 仅 选择排序 并且

随机推荐

  • Linux三级 学习笔记(二)计算机体系结构与操作系统-操作系统

    1 4 操作系统的基本概念 1 4 1 操作系统的定义和作用 操作系统的作用可以从用户和系统俩个不同角度来看 用户视角 系统视角 1 用户视角 操作系统为用户提供的服务有 程序开发 程序运行 I O设备访问 文件访问 系统资源访问 错误检测
  • App 抓包提示网络异常怎么破?

    背景 当你测试 App 的时候 想要通过 Fiddler Charles 等工具抓包看下 https 请求的数据情况 发现大部分的 App 都提示网络异常 无数据等等信息 以 贝壳找房 为例 Fiddler 中看到的请求是这样的 你可能开始
  • 已安装 MySQL,但执行 mysql 命令提示命令找不到!

    因个人需要 在阿里购买了一个轻量应用服务器 服务器配好 LAMP 环境 但奇怪是的我想登录 MySql 却提示命令找不到 查看 MySQL 运行状态 却是 Active running 提交了阿里工单 可是感觉客服是答非所问 我也是很无奈
  • Windows Terminal 和 WSL 安装及配置

    一 打开开发者选项和传递优化 二 在Microsoft Store安装Windows Terminal和Ubuntu子系统 三 配置 Windows Terminal配置 打开settings json配置文件 修改如下 此项用来配置打开W
  • 重磅!瞄准 Web 3.0,谷歌云推出专为区块链服务的 Blockchain Node Engine!

    本文由 Cloud Ace 整理发布 更多内容请访问 Cloud Ace 官网 区块链技术正在为世界各地的消费者和企业带来巨大的创新和价值创造 随着技术变得越来越主流 公司需要可扩展 安全和可持续的基础设施来发展业务并支持他们的网络 谷歌云
  • LeetCode-1124. 表现良好的最长时间段【哈希表,前缀和,单调栈】

    LeetCode 1124 表现良好的最长时间段 哈希表 前缀和 单调栈 题目描述 解题思路一 查字典 cur是当前的前缀和 劳累与不劳累天数之差 向前遍历 有两种情况 情况一 若cur大于0则是 0 i 的劳累与不劳累天数之差一定最大 记
  • Angular知识整合一:Angular中的组件和一些基本概念

    什么是Angular Angular是一个基于TypeScript构建的开发平台 它包括一下三个部分 一个基于组件的库 一组涵盖路由 表单管理 客户端服务端通信等各种功能继承的库 一套开发 构建 测试 更新代码的工具 Angular中的知识
  • matlab练习程序(渲染三原色)

    这里我用的空间是x向右为正 y向下为正 z向屏幕里面为正 相当于标准右手系绕x轴旋转了180度 将三个点光源放在 r 0 3 0 0 5 g 0 3 0 5 cos pi 6 0 5 sin pi 6 b 0 3 0 5 cos pi 6
  • 练习-Java继承和多态之接口

    提示 文章写完后 目录可以自动生成 如何生成可参考右边的帮助文档 题目 任务 编写一个学校接待方面的程序 招待不同身份的人的食宿问题 编程要求 仔细阅读右侧编辑区内给出的代码框架及注释 在 Begin End 中编写一个学校接待方面的程序
  • 什么是电力系统的功率平衡?为什么在任何时候要保持电力系统的功率平衡?

    什么是电力系统的功率平衡 为什么在任何时候要保持电力系统的功率平衡 答 电力系统的功率平衡是指电力有功功率和无功功率的平衡 这种功率平衡也就是电力供需平衡 要求电力系统发送的功率与系统的负荷需要随时保持平衡 电能的一个最重要特点就是不能储存
  • 关于Vue.js中数据模型的绑定以及方法事件的绑定与调用

    在vue js中 我们可以将事件方法写在methods属性中 数据模型在data中定义 Vue的基本结构如下 只写最常用的 将数据与vue实例绑定通过v bind标签 这里绑定的是sourceId这个值 基于vue的双向绑定 如果要取vue
  • 蓝桥杯:整除序列

    整除序列 15分 题目描述 有一个序列 序列的第一个数是 n 后面的每个数是前一个数整除 2 请输出这个序列中值为正数的项 输入格式 输入一行包含一个整数 n 输出格式 输出一行 包含多个整数 相邻的整数之间用一个空格分隔 表示答案 评测用
  • 虚拟环境安装和操作

    文章目录 安装相应库和配置 查看已安装虚拟环境 创建虚拟环境 切换 进入虚拟环境 退出虚拟环境 虚拟环境 linux创建Python虚拟环境及配置 Django Flask项目中如何创建Python虚拟环境呢 汇总 环境迁移 安装相应库和配
  • 攻防世界MISC刷题1-50

    目录 1 ext3 2 base64stego 3 功夫再高也怕菜刀 4 easycap 5 reverseMe 6 Hear with your Eyes 7 What is this 8 normal png 9 something i
  • idea 添加 VUE 的语法支持和开发

    一 VUE的开发分两种 一种是直接在HTML文件中使用 一种是VUE文件的形式开发 1 首先我们先让 HTML 文件支持 VUE 的语法指令提示 2 File gt Setting gt Edit gt Inspections gt htm
  • 父类A a = new 子类B

    父类名 a new 子类名 子类名 b new 子类名 比较上面两种创建实例的区别 a只能调用父类的函数 和子类重写父类的方法 不能调用父类中不存在的子类的函数 因为它没有继承 a是父类的引用 指向了一个子类对象 好处如果一旦发现该B对象无
  • Jetson Orin NX install Fastdeploy

    FastDeploy jetson md at develop PaddlePaddle FastDeploy GitHub sudo apt get install gcc sudo apt get install cmake git c
  • postman-token的作用

    Postman生成的代码中的postman token是什么 What is the postman token in generated code from Postman 这主要用于绕过Chrome 等其他浏览器 中的错误 如果XMLH
  • QEMU/KVM PCI Passthrough(i350) & DPDK 网络性能测试

    QEMU KVM PCI Passthrough i350 DPDK 网络性能测试 硬件要求 CPU必须支持硬件虚拟化 Intel VT d or AMD Vi 和 IOMMU 原图链接 主机配置 设置iommu IOMMU kernel
  • kmp算法(最简单最直观的理解,看完包会)

    本文将以特殊的方式来让人们更好地理解kmp算法 不包括kmp算法的推导 接下来 我们将从朴素算法出发 在这之前 我们先设主串为S 模式串为T 我们要解决的询问是主串中是否包含模式串 即T是否为S的子串 版权声明 本文为原创文章 转载请标明出