字符串 | 字符串匹配之 KMP 算法以及 C++ 代码实现

目录

    • 1 为什么使用 KMP?
    • 2 什么是 next 数组?
      • 2.1 什么是字符串的前后缀?
      • 2.2 如何计算 next 数组?
    • 3 KMP 部分的算法
    • 4 完整代码


😈前言:这篇文章比较长,但我感觉自己是讲明白了的



1 为什么使用 KMP?

首先声明,参与字符串匹配的两个串分别叫 “主串” 和 “模式串”。

答:在朴素字符串匹配中,若主串 s[i] 不等于模式串 p[j],那么需要主串和模式串都回溯到开头,模式串要从主串的下一个字符开始重新匹配。KMP 算法之所以被提出,是因为它不需要主串回溯,且只需要模式串部分回溯,节省了很多不必要也不可能成功的匹配操作。

这里只是大概讲了一下原因,看不懂也很正常。



2 什么是 next 数组?

next 数组是 KMP 算法中的一个辅助工具,我们只需要知道它是什么,以及它是怎么被计算出来的即可。

  • 定义:next 数组的值代表的是字符串的前缀与后缀相同的最大长度。


2.1 什么是字符串的前后缀?

前缀和后缀的定义:

  • 前缀:除最后一个字符以外的,字符串的所有头部子串;
  • 后缀:除第一个字符以外的,字符串的所有尾部子串;

强调:不管是前缀还是后缀,字符都是按从左往右的顺序数的!

假设有如下的字符串:

在这里插入图片描述

  • 设 next[0] = -1;
  • “A” 的前缀和后缀都为空集,共有元素的长度为 0,设 next[1] = 0;
  • “AB” 的前缀为 [A],后缀为 [B],共有元素的长度为 0,设 next[2] = 0;
  • “ABA” 的前缀为 [A, AB],后缀为 [BA, A],共有元素的长度为 1,设 next[3] = 1;
  • “ABAC” 的前缀为 [A, AB, ABA],后缀为 [BAC, AC, C],共有元素的长度为 0,设 next[4] = 1;
  • 以此类推

说明:next[0] = -1 是 KMP 算法的特殊需求,到时候帮助模式串回溯到开头。

上述字符串的 next 数组如下:

在这里插入图片描述

可以看出,字符 “D” 对应的 next 值是 0,即字符串 “ABAC” 的前缀与后缀相同的最大长度。

我主要想强调的是,每个字符对应的 next 值,是该字符之前的字符串的前缀与后缀相同的最大长度,并不包含该字符。



2.2 如何计算 next 数组?

直接上代码:

vector<int> getNextArr(string p) {vector<int> next(p.size(), 0);int i = -1, j = 0;next[0] = -1;while (j < p.size() - 1) {if (i == -1 || p[i] == p[j]) {++i;++j;next[j] = i;} else {i = next[i];}}return next;
}

我们很自然地想到使用 i 和 j 一前一后两个指针来完成字符串的匹配,因此定义 i = -1 和 j = 0 作为初始值。

① 情况一:当 i = -1 时

a)说明匹配还没有开始,因为 i 还没有指向任何字符。因此我们让 i 和 j 均右移一格,分别指向两个字符。根据 2.1 节介绍的规则,因为由一个字符构成的字符串是没有前后缀的,所以 next[1] 的值为 0:

next[j] = i;  // j=1, i=0

b)说明上一次的匹配彻底失败了,即因为反复执行 i=next[i] 而导致 i 回溯到了 -1 的位置。此时:

++i;
++j;
next[j] = i;  // i=0

next[j] 的值为 0,满足 j 指向的字符前面的字符串的前后缀最大匹配长度为 0 的情况。

这里先不用纠结 i 是怎么回溯的,只需要知道当 i = -1 时的代码没有问题即可。

② 情况二:当 p[i] == p[j] 时

说明 i 和 j 指向的字符相同。因此我们也让 i 和 j 均右移一格,分别指向两个新的字符。同时,使用以下代码计算 next 的值:

next[j] = i;

说明:不管是情况一还是情况二,i 的值其实都代表的是已经匹配了的前后缀长度。因为只有匹配了,i 才会向右移嘛!否则 i 会一直回溯,直至回溯到 -1 的位置!

③ 情况三:最恶心的 else 情况

说明匹配已经开始了,但 i 和 j 指向的字符不同。比如,下图所代表的时刻:

在这里插入图片描述

其中,绿色部分和黄色部分代表的是 “ABACDABA” 这个字符串中成功匹配的部分。

虽然在这种情况下匹配失败了,但是我们可以考虑比该情况更差的情况。已知字符串 “ABACDABA” 中的最长匹配长度为 3,如果我们再纳入 j 指向的字符 “B”,是没有办法再续辉煌的!即没有办法在最长匹配长度 3 的基础上再加 1!

但是我们可以考虑字符串 “ABACDABA” 中匹配长度为 2 的部分,看看该部分是否有机会在加上 “B” 后仍然匹配。

我来具体讲一下上述思路,可以参考下图:

在这里插入图片描述

其中 ① 是最理想的情况,即最长匹配部分 “ABA” 分别加上 i 指向的字符和 j 指向的字符后仍然匹配,但显然在上述例子中是不匹配的。那么我们接着考虑匹配长度比 3 小的情况。情况 ② 是根本不会被考虑的,因为前缀 “AB” 和后缀 “BA” 根本不匹配。再来看情况 ③,前缀 “A” 和后缀 “A” 匹配,同时 i 指向的字符和 j 指向的字符相同。因此,我们捡漏到了一种匹配的情况!

刚才我们有提到 “情况 ② 是根本不会被考虑的”,那么 KMP 算法具体是如何规避的呢?核心是如下代码:

i = next[i];

最初,我觉得简直无法理解,但经过上述分析后我恍然大悟!请看下图:

在这里插入图片描述

针对情况 ①,一个重要的信息是绿色部分和黄色部分是完全相同的!我们可以把 “找两个 ‘ABA’ 之间更短的匹配部分” 转换为 “找一个 ‘ABA’ 中的匹配部分”!又由于绿色部分的匹配长度在此前已经被计算出了,等于 next[i] 的值,即 “C” 前面的字符串 “ABA” 中前后缀的最大匹配长度。因此,我们可以直接让 i 指针飞到 next[i] 处,即从情况 ① 飞到情况 ③:

在这里插入图片描述

飞到情况 ③ 后,前缀 “A” 和后缀 “A” 是匹配的,原因我们刚才已经分析过了。接下来就看 “A” 分别加上 i 指向的字符和 j 指向的字符后是否仍然匹配了。

以上就是对计算 next 数组的代码的全部分析!



3 KMP 部分的算法

直接上代码:

int kmp(string s, string p) {vector<int> next = getNextArr(p);int n = s.size();int m = p.size();int i = 0, j = 0;while (i < n && j < m) {if (j == -1 || s[i] == p[j]) {++i;++j;} else {j = next[j];}}if (j == m) {return i - m;}return -1;
}

其中 i 是主串 s 的指针,j 是模式串 p 的指针。

① 情况一:当 s[i] == p[j] 时

说明 i 和 j 指向的字符相同。因此我们也让 i 和 j 均右移一格,分别指向两个新的字符。

这是最简单易懂的情况。

② 情况二:else 情况

说明 i 和 j 指向的字符不同,但还有拯救的希望。参见下图的例子:

在这里插入图片描述

先看情况 ①,其中的绿色部分和黄色部分(深浅部分都包括)代表主串和模式串匹配的部分。特别地,深色部分代表主串和模式串自己内部的匹配部分。如上图所示,i 和 j 指向的字符不同,如果是朴素字符串匹配算法,那么两个指针都得回到开头以重新进行匹配。而在 KMP 算法中,主串不需要回溯,而模式串也只需要回溯一部分。

与 2.2 节的分析相同,既然我们没有办法让 “CACCA” 分别加上 i 和 j 指向的字符后仍然相同,那么我们可以考虑一个比它短的部分,即绿色部分的一个后缀。当然这个后缀不能乱选,我们还是得有一点依据。这个依据就是:绿色部分的后缀要和黄色部分的前缀一样!否则将无法匹配。

上述思路可以参见下图:

在这里插入图片描述

由于 “CACCA” 没有办法在分别加上 i 和 j 指向的字符后仍然相同,那么我们可以考虑蓝色虚线部分,即一个比 “CACCA” 短的部分,分别是绿色部分的后缀和黄色部分的前缀。接下来,我们就来 “找匹配的绿色部分后缀和黄色部分前缀”。

此时,一个重要的信息是绿色部分和黄色部分是完全相同的!我们可以把 “找匹配的绿色部分后缀和黄色部分前缀” 转换为 “找匹配的黄色部分后缀和黄色部分前缀”!又由于我们先前为模式串计算了 next 数组,因此这是非常容易得到的!核心代码如下:

j = next[j];

参见情况 ①,next[j] 代表 j 指向的字符 “E” 前面的字符串 “CACCA” 中的前后缀匹配长度,同时代表满足要求的前缀的长度。因此,我们让 j 移动到 next[j],如情况 ② 所示。然后判断 “CA” 分别加上 i 和 j 指向的字符后是否仍然相同。

③ 情况三:当 j == -1 时

说明 i 和 j 指向的字符不同,且没有拯救的希望。具体来说,出现了多次情况二,导致 j 回溯到了 -1 位置。在这种情况下,i 和 j 均需要右移一格,以指向两个新的字符进行比较。也就是说,i 指向了主串的下一个字符,j 指向了模式串的第一个字符,以重新开始比较。

在情况二中,i 是不会后移的,只有 j 在回溯;在情况三中,由于 i 不后移就不会有匹配的希望,因此 i 需要右移一格。



4 完整代码

#include <bits/stdc++.h>using namespace std;vector<int> getNextArr(string p) {vector<int> next(p.size(), 0);int i = -1, j = 0;next[0] = -1;while (j < p.size() - 1) {if (i == -1 || p[i] == p[j]) {++i;++j;next[j] = i;} else {i = next[i];}}return next;
}int kmp(string s, string p) {vector<int> next = getNextArr(p);int n = s.size();int m = p.size();int i = 0, j = 0;while (i < n && j < m) {if (j == -1 || s[i] == p[j]) {++i;++j;} else {j = next[j];}}if (j == m) {return i - m;}return -1;
}int main() {string s = "aaaaa";string p = "bba";vector<int> next = getNextArr(p);for (auto & n : next) {cout << n << ' ';}cout << kmp(s, p);return 0;
}


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

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

相关文章

让低代码平台插上AI的翅膀 - 记开源驰骋AI平台升级

让低代码系统插上AI的翅膀——驰骋低代码开发平台引领新时代 在当今日新月异的科技世界中&#xff0c;人工智能&#xff08;AI&#xff09;已经成为各个行业不可或缺的一部分。从制造业的自动化生产到金融行业的智能风控&#xff0c;再到医疗领域的精准诊断&#xff0c;AI技术…

Kafka自定义分区器编写教程

1.创建java类MyPartitioner并实现Partitioner接口 点击灯泡选择实现方法&#xff0c;导入需要实现的抽象方法 2.实现方法 3.自定义分区器的使用 在自定义生产者消息发送时&#xff0c;属性配置上加入自定义分区器 properties.put(ProducerConfig.PARTITIONER_CLASS_CONFIG,&q…

基于STM32的轻量级Web服务器设计

文章目录 一、前言1.1 开发背景1.2 实现的功能1.3 硬件模块组成1.4 ENC28J60网卡介绍1.5 UIP协议栈【1】目标与特点【2】核心组件【3】应用与优势 1.6 添加UIP协议栈实现创建WEB服务器步骤1.7 ENC28J60添加UIP协议栈实现创建WEB客户端1.8 ENC28J60移植UIP协议并编写服务器测试示…

NetMizer 日志管理系统前台RCE漏洞

声明 本文仅用于技术交流&#xff0c;请勿用于非法用途 由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c;文章作者不为此承担任何责任。 一、产品介绍 NetMizer日志管理系统是一个与NetMizer流量管理设备配合…

【开源】Wordpress自定义鼠标样式插件

插件简介 使用此插件可一键自定义Wordpress前端鼠标指针样式。利用该插件&#xff0c;站长可以快速实现替换多种鼠标指针样式于网站前端。 鼠标图案均来自于网络&#xff0c;插件仅作收集整理&#xff0c;插件完全开源无任何商业性质。 插件截图 使用教程 下载插件文件 下载…

文件系统小册(FusePosixK8s csi)【2 Posix标准】

文件系统小册&#xff08;Fuse&Posix&K8s csi&#xff09;【2 Posix】 往期文章&#xff1a;文件系统小册&#xff08;Fuse&Posix&K8s csi&#xff09;【1 Fuse】 POSIX&#xff1a;可移植操作系统接口&#xff08;标准&#xff09; 1 概念 POSIX&#xff1a;…

前端Vue自定义支付密码输入框键盘与设置弹框组件的设计与实现

摘要 随着信息技术的不断发展&#xff0c;前端开发的复杂性日益加剧。传统的开发方式&#xff0c;即将整个系统构建为一个庞大的整体应用&#xff0c;往往会导致开发效率低下和维护成本高昂。任何微小的改动或新功能的增加都可能引发对整个应用逻辑的广泛影响&#xff0c;这种…

【原创】springboot+mysql医院预约挂号管理系统设计与实现

个人主页&#xff1a;程序猿小小杨 个人简介&#xff1a;从事开发多年&#xff0c;Java、Php、Python、前端开发均有涉猎 博客内容&#xff1a;Java项目实战、项目演示、技术分享 文末有作者名片&#xff0c;希望和大家一起共同进步&#xff0c;你只管努力&#xff0c;剩下的交…

【Redis】List源码剖析

大家好&#xff0c;我是白晨&#xff0c;一个不是很能熬夜&#xff0c;但是也想日更的人。如果喜欢这篇文章&#xff0c;点个赞&#x1f44d;&#xff0c;关注一下&#x1f440;白晨吧&#xff01;你的支持就是我最大的动力&#xff01;&#x1f4aa;&#x1f4aa;&#x1f4aa…

游戏交易平台源码游戏帐号交易平台系统源码

功能介绍 1&#xff1a;后台可以添加删除游戏分类 2&#xff1a;会员中心可以出售游戏币,账号&#xff0c;装备 3&#xff1a;后台可以对会员和商品进行管理 4&#xff1a;多商家入驻,商家发布信息 5&#xff1a;手机版功能可以生成APP 6&#xff1a;在线支付可支持微信和支…

VQAScore开启文本到视觉生成评估新篇章

随着生成式人工智能技术的飞速发展&#xff0c;如何全面评估生成内容的质量和与输入提示的一致性成为了一个挑战。在图像-文本对齐领域&#xff0c;传统的评估方法如CLIPScore存在局限性&#xff0c;尤其是在处理涉及多个对象、属性和关系的复杂提示时。它们通常基于简单的词袋…

MES系统的功能、架构及应用价值

MES系统生产过程控制的主要方面涵盖了生产计划与控制、生产调度与排程、数据采集与监控、质量控制与管理、物料管理与控制以及设备管理与维护等多个方面。这些功能共同构成了MES系统的核心价值&#xff0c;帮助企业实现生产过程的数字化、智能化和精细化管理。 一、工厂使用MES…

Nginx 1.26.0 爆 HTTP/3 QUIC 漏洞,建议升级更新到 1.27.0

据悉&#xff0c;Nginx 1.25.0-1.26.0 主线版本中涉及四个与 NGINX HTTP/3 QUIC 模块相关的中级数据面 CVE 漏洞&#xff0c;其中三个为 DoS 攻击类型风险&#xff0c;一个为随机信息泄漏风险&#xff0c;影响皆为允许未经身份认证的用户通过构造请求实施攻击。目前已经紧急发布…

密码与网络安全(一):专栏导读

1.专栏目的 这个专栏的核心目的是提升博主自己的密码与网络安全知识&#xff0c;其次也想将相关的学习收获分享给感兴趣的小伙伴。博主自己的工作主要量子技术相关&#xff0c;身边的同事基本上也是物理专业出身&#xff0c;最近和单位密码领域同事聊天时他提到一个思路很好的启…

【Linux 网络】网络基础(三)(其他重要协议或技术:DNS、ICMP、NAT)

一、DNS&#xff08;Domain Name System&#xff09; DNS 是一整套从域名映射到 IP 的系统。 1、DNS 背景 TCP/IP 中使用 IP 地址和端口号来确定网络上的一台主机的一个程序&#xff0c;但是 IP 地址不方便记忆。于是人们发明了一种叫主机名的东西&#xff0c;是一个字符串&…

学习笔记——网络参考模型——TCP/IP模型

二、TCP/IP模型 TCP/IP模型(TCP/IP协议栈)&#xff1a;很多个互联网协议的集合&#xff0c;其中以TCP和IP为主&#xff0c;将这些协议的集合称为TCP/IP协议栈。目前使用最多的协议模型。 因为OSI协议栈比较复杂&#xff0c;且TCP和IP两大协议在业界被广泛使用&#xff0c;所以…

JavaScript 动态网页实例 —— 窗口控制

除了打开和关闭窗口之外,还有很多其他控制窗口的方法。例如,可以使用 window.focus()方法使窗口获得焦点,也可以利用与其相对的window.blur 方法使窗口失去焦点。本节介绍移动窗口、改变窗口大小、窗口滚动、窗口超时操作、常用窗口事件、常用窗口扩展等窗口控制的方法和手段。…

[每周一更]-(第99期):MySQL的索引为什么用B+树?

文章目录 B树与B树的基本概念B树&#xff08;Balanced Tree&#xff09;B树&#xff08;B-Plus Tree&#xff09;对比 为什么MySQL选择B树1. **磁盘I/O效率**2. **更稳定的查询性能**3. **更高的空间利用率**4. **并发控制** 其他树结构的比较参考 索引是一种 数据结构&#x…

LeeCode热题100(两数之和)

本文纯干货&#xff0c;看不懂来打我&#xff01; 自己先去看一下第一题的题目两数之和&#xff1a;. - 力扣&#xff08;LeetCode&#xff09; 简单来说就是让你在一个数组里面找两个数&#xff0c;这两个数的和必须满足等于目标值target才行。 我认为你要是没有思路的话&a…

营造科技展厅主题氛围,多媒体应用有哪些新策略?

长久以来&#xff0c;展厅作为线下向公众传递信息的窗口&#xff0c;其设计风格与内容主题紧密相连&#xff0c;展现出千姿百态的面貌。然而&#xff0c;随着数字多媒体技术的日新月异&#xff0c;展厅不再仅仅是传统的信息展示平台&#xff0c;而是成为了引领内容展示潮流的风…