基础算法--KMP字符串

KMP 算法是一个快速查找匹配串的算法,它的作用其实就是本题问题:如何快速在「原字符串」中找到「匹配字符串」。
在朴素解法中,不考虑剪枝的话复杂度是 O(m∗n) 的,而 KMP 算法的复杂度为 O(m+n)。

KMP 之所以能够在O(m+n) 复杂度内完成查找,是因为其能在「非完全匹配」的过程中提取到有效信息进行复用,以减少「重复匹配」的消耗。

你可能不太理解,没关系,我们可以通过举个例子来理解 KMP。

1. 匹配过程

在模拟 KMP 匹配过程之前,我们先建立两个概念:

  • 前缀:对于字符串 abcxxxxefg,我们称 abc 属于 abcxxxxefg 的某个前缀。
  • 后缀:对于字符串 abcxxxxefg,我们称 efg 属于 abcxxxxefg 的某个后缀。

然后我们假设原串为 abeababeabf,匹配串为 abeabf
在这里插入图片描述

先看看如果不使用 KMP,会如何进行匹配(不使用 substring 函数的情况下)。
首先在「原串」和「匹配串」分别各自有一个指针指向当前匹配的位置。
首次匹配的「发起点」是第一个字符 a 。显然,后面的 abeab 都是匹配的,两个指针会同时往右移动(黑标)。
在都能匹配上 abeab 的部分,「朴素匹配」和「KMP」并无不同。
在这里插入图片描述

直到出现第一个不同的位置(红标):
接下来,正是「朴素匹配」和「KMP」出现不同的地方:

先看下「朴素匹配」逻辑:

  • 将原串的指针移动至本次「发起点」的下一个位置(b 字符处)(遍历原串);匹配串的指针移动至起始位置。
  • 尝试匹配,发现对不上,原串的指针会一直往后移动,直到能够与匹配串对上位置。

如图:
在这里插入图片描述

也就是说,对于「朴素匹配」而言,一旦匹配失败,将会将原串指针调整至下一个「发起点」,匹配串的指针调整至起始位置,然后重新尝试匹配。
这也就不难理解为什么「朴素匹配」的复杂度是O(m∗n) 了。

然后我们再看看「KMP 匹配」过程:
在这里插入图片描述

首先匹配串会检查之前已经匹配成功的部分中里是否存在相同的「前缀」和「后缀」。如果存在,则跳转到「前缀」的下一个位置继续往下匹配:
在这里插入图片描述
跳转到下一匹配位置后,尝试匹配,发现两个指针的字符对不上,并且此时匹配串指针前面不存在相同的「前缀」和「后缀」,这时候只能回到匹配串的起始位置重新开始。

到这里,你应该清楚 KMP 为什么相比于朴素解法更快:

因为 KMP 利用已匹配部分中相同的「前缀」和「后缀」来加速下一次的匹配。
因为 KMP 的原串指针不会进行回溯(没有朴素匹配中回到下一个「发起点」的过程)。

第一点很直观,也很好理解。
我们可以把重点放在第二点上,原串不回溯至「发起点」意味着什么?

其实是意味着:随着匹配过程的进行,原串指针的不断右移,我们本质上是在不断地在否决一些「不可能」的方案。当我们的原串指针从 i 位置后移到 j 位置,不仅仅代表着「原串」下标范围为 [i,j) 的字符与「匹配串」匹配或者不匹配,更是在否决那些以「原串」下标范围为 [i,j)为「匹配发起点」的子集。

2. 分析实现

我们分析一下复杂度。如果严格按照上述解法的话,最坏情况下我们需要扫描整个原串,复杂度为 O(n)。同时在每一次匹配失败时,去检查已匹配部分的相同「前缀」和「后缀」,跳转到相应的位置,如果不匹配则再检查前面部分是否有相同「前缀」和「后缀」,再跳转到相应的位置 … 这部分的复杂度是 O(m^2),因此整体的复杂度是 O(n * m^2),而我们的朴素解法是 O(m * n)O(m∗n) 的。

说明还有一些性质我们没有利用到。

显然,扫描完整原串操作这一操作是不可避免的,我们可以优化的只能是「检查已匹配部分的相同前缀和后缀」这一过程。

再进一步,我们检查「前缀」和「后缀」的目的其实是「为了确定匹配串中的下一段开始匹配的位置」。

同时我们发现,对于匹配串的任意一个位置而言,由该位置发起的下一个匹配点位置其实与原串无关。

举个 例子,对于匹配串 abcabd 的字符 d 而言,由它发起的下一个匹配点跳转必然是字符 c 的位置。因为字符 d 位置的相同「前缀」和「后缀」字符 ab 的下一位置就是字符 c。

可见从匹配串某个位置跳转下一个匹配位置这一过程是与原串无关的,我们将这一过程称为找 next 点。

显然我们可以预处理出 next 数组,数组中每个位置的值就是该下标应该跳转的目标位置( next 点)。

当我们进行了这一步优化之后,复杂度是多少呢?

预处理 next 数组的复杂度未知,匹配过程最多扫描完整个原串,复杂度为 O(n)。

因此如果我们希望整个 KMP 过程是 O(m + n)的话,那么我们需要在 O(m)的复杂度内预处理出 next数组。

所以我们的重点在于如何在 O(m)复杂度内处理处 next 数组。

3. next 数组的构建

接下来,我们看看 next 数组是如何在 O(m)的复杂度内被预处理出来的。

假设有匹配串 aaabbab,我们来看看对应的 next 是如何被构建出来的。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
这就是整个 next 数组的构建过程,时空复杂度均为 O(m)。

至此整个 KMP 匹配过程复杂度是 O(m + n)的。

4.算法实现

python代码

def build_next(patt):"""计算 Next 数组"""next = [0]  # next 数组(初值元素一个0)prefix_len = 0  # 当前共同前后缀的长度i = 1while i < len(patt):if patt[prefix_len] == patt[i]:prefix_len += 1next.append(prefix_len)i += 1else:if prefix_len == 0:next.append(0)i += 1else:prefix_len = next[prefix_len - 1]return nextdef kmp_search(string, patt):next = build_next(patt)i = 0  # 主串中的指针j = 0  # 子串中的指针while i < len(string):if string[i] == patt[j]:  # 字符匹配, 指针后移i += 1j += 1elif j > 0:  # 字符失配,根据 next 跳过字串前面的一些字符j = next[j - 1]else:  # 子串第一个字符就失配i += 1if j == len(patt): # 匹配成功return i - jstring = "ABABABCAA"
patt = "ABABC"
ind = kmp_search(string, patt)
print(ind)

acwing 831 KMP字符串

#include <iostream>
#include <string>using namespace std;const int N = 10010;
const int M = 100010;
int ne[N];//把每一个点为终点的最长相同前缀和后缀的长度存在里面
char s[M], p[N];int main() {int n, m;cin >> n >> p + 1 >> m >> s + 1;ne[1] = 0;//这里从1开始for (int i = 2, j = 0; i <= n; ++i) {while (j && p[j + 1] != p[i]) j = ne[j];if (p[j + 1] == p[i]) j++;ne[i] = j;}for (int i = 1, j = 0; i <= m; ++i) {while (j && s[i] != p[j + 1]) j = ne[j];if (s[i] == p[j + 1]) j++;if (j == n) {j = ne[j];cout << i - n << ' ';}}return 0;
}

5.总结

KMP 算法的应用范围要比 Manacher 算法广,Manacher 算法只能应用于「回文串」问题,相对比较局限,而「子串匹配」问题还是十分常见的。

背过这样的算法的意义在于:相当于大脑里有了一个时间复杂度为 O(n) 的 api 可以使用,这个 api 传入一个原串和匹配串,返回匹配串在原串的位置。

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

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

相关文章

leetCode 213. 打家劫舍 II 动态规划 房间连成环怎么偷呢?

213. 打家劫舍 II - 力扣&#xff08;LeetCode&#xff09; 你是一个专业的小偷&#xff0c;计划偷窃沿街的房屋&#xff0c;每间房内都藏有一定的现金。这个地方所有的房屋都 围成一圈 &#xff0c;这意味着第一个房屋和最后一个房屋是紧挨着的。同时&#xff0c;相邻的房屋装…

websocket连接实例

websocket连接 此篇websocket是最简单的运用&#xff0c;虽然简单&#xff0c;但也是需要注意 还有其它方法货协议&#xff0c;比如socket.js等。今天时间不充裕虽然例子都写好了&#xff0c;下次更新websocket所有相关的东西。提前想了解的&#xff0c;可私信 前端 var webs…

Spring Cloud Stream Kafka(3.2.2版本)使用

问题 正在尝试只用Spring Cloud Stream Kafka。 步骤 配置 spring:cloud:function:definition: project2Building stream:kafka:binder:brokers: xxxx:9002configuration:enable.auto.commit: falsesession.timeout.ms: 30000max.poll.records: 30allow.auto.create.top…

PHP Web 开发基础

PHP是动态类型的Web开发的脚本语言&#xff0c;PHP以页面文件作为加载和运行的单元&#xff0c;PHP现在有了Composer作为开发包管理。 1.使用Composer管理依赖 自从.NET开发用了Nuget管理程序集依赖&#xff0c;我就再也离不开它了&#xff0c;幸亏Java中也有Maven管理jar包&…

如何定时备份使用Docker构建的MySQL容器中的数据库

&#x1f468;&#x1f3fb;‍&#x1f4bb; 热爱摄影的程序员 &#x1f468;&#x1f3fb;‍&#x1f3a8; 喜欢编码的设计师 &#x1f9d5;&#x1f3fb; 擅长设计的剪辑师 &#x1f9d1;&#x1f3fb;‍&#x1f3eb; 一位高冷无情的编码爱好者 大家好&#xff0c;我是 DevO…

PIE:1979-2018年中国气温数据产品(空间分辨率为0.1º)

简介 中国气温数据产品包含1979-2018年期间中国的近地表气温数据&#xff08;单位为摄氏度&#xff09;&#xff0c;时间分辨率为每日&#xff0c;空间分辨率为0.1。本产品集成了再分析数据&#xff08;ERA5、CMFD&#xff09;、遥感数据&#xff08;MODIS&#xff09;、原位数…

php eayswoole node axios crypto-js 实现大文件分片上传复盘

不啰嗦 直接上步骤 步骤1.开发环境配置 项目需要node.js 做前端支撑 官网下载地址&#xff1a; http://nodejs.cn/download/ 根据自己需要下载对应的版本,我下载的是windows系统64位的版本。 包下载好后 进行安装&#xff0c;安装步骤在此省略... 测试是否安装成功 …

蓝海彤翔亮相2023新疆网络文化节重点项目“新疆动漫节”

9月22日上午&#xff0c;2023新疆网络文化节重点项目“新疆动漫节”&#xff08;以下简称“2023新疆动漫节”&#xff09;在克拉玛依科学技术馆隆重开幕&#xff0c;蓝海彤翔作为国内知名的文化科技产业集团应邀参与此次活动&#xff0c;并在美好新疆e起向未来动漫展映区设置展…

C#生成自定义海报

安装包 SixLabors.ImageSharp.Drawing 2.0 需要的字体&#xff1a;宋体和微软雅黑 商用的需要授权如果商业使用可以使用方正书宋、方正黑体&#xff0c;他们可以免费商用 方正官网 代码 using SixLabors.Fonts; using SixLabors.ImageSharp; using SixLabors.ImageSharp.Draw…

python切分字符串

在Python中&#xff0c;您可以使用不同的方法来切分字符串&#xff0c;具体取决于您的需求和字符串的结构。以下是一些常见的方法&#xff1a; 使用split()方法&#xff1a; split()方法允许您根据指定的分隔符将字符串切分成子字符串&#xff0c;并返回一个包含这些子字符串的…

使用SPY++查看窗口信息去排查客户端UI软件问题

目录 1、使用SPY查看窗口的信息 2、使用SPY查看某些软件UI窗口用什么UI组件实现的 2.1、查看海康视频监控客户端安装包程序 2.2、查看华为协同办公软件WeLink 2.3、查看字节协同办公软件飞书 2.4、查看最新版本的Chrome浏览器 2.5、查看小鱼易连视频会议客户端软件 2.6…

C/S架构学习之UDP服务器

UDP服务器的实现流程&#xff1a;一、创建用户数据报套接字&#xff08;socket函数&#xff09;&#xff1a;通信域选择IPV4网络协议、套接字类型选择数据报式&#xff1b; int sockfd socket(AF_INET,SOCK_DGRAM,0); 二、填充服务器的网络信息结构体&#xff1a;1.定义网络信…

Java初始化大量数据到Neo4j中(一)

背景&#xff1a;我们项目第一次部署图数据库&#xff0c;要求我们把现有的业务数据以及关系上线第一时间初始化到Neo4j中。开发环境数据量已经百万级别。生成环境数据量更多。 我刚开始开发的时候&#xff0c;由于对Neo4j的了解并没有很多&#xff0c;第一想到的是用代码通用组…

Mybatis 批量修改数据,,并判断非空数据插入

方法一&#xff1a; <update id"updateListPO"> <foreach collection"list" separator";" item"item">UPDATE project_quotation_item<SET>product_num #{item.productNum},product_price_total #{item.pr…

CIP或者EtherNET/IP中的PATH是什么含义?

目录 SegmentPATH举例 最近在学习EtherNET/IP&#xff0c;PATH不太明白&#xff0c;翻了翻规范&#xff0c;在这里记个笔记。下面的叙述可能是中英混合&#xff0c;有一些是规范中的原文我直接搬过来的。我翻译的不准确。 Segment PATH是CIP Segment中的一个分类。要了解PATH…

每日一题 322零钱兑换(完全背包)(灵神版本)

题目 与01背包的区别就是可以重复拿一件物品 零钱兑换 给你一个整数数组 coins &#xff0c;表示不同面额的硬币&#xff1b;以及一个整数 amount &#xff0c;表示总金额。 计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额&#xff…

Dev C++安装与运行

参考: https://blog.csdn.net/Keven_11/article/details/126388791 https://www.cnblogs.com/-Wallace-/p/cpp-stl.html 2021年真题要求 2022年真题要求 河南省的考试环境 IDE环境 Dev C 安装 下载 安装 点击OK&#xff0c;选择我接受 修改安装路径为D盘d:\Program Fi…

MQTT协议是什么?快速了解MQTT协议在物联网中的应用

随着工业互联网的迅猛发展&#xff0c;工业设备数据采集和实时监控成为制造业提高生产效率和质量的重要手段。在物联网应用中&#xff0c;通信技术包括Wi-Fi、RFID、NFC、RS232、RS485、USB等&#xff0c;其中在物联网技术框架体系中所使用到的通讯协议主要有&#xff1a;AMQP、…

[ruby on rails] postgres sql explain 优化

一、查看执行计划 sql User.all.to_sql # 不会实际执行查询 puts ActiveRecord::Base.connection.explain(sql)# 会实际执行查询&#xff0c;再列出计划 User.all.explain# 会实际执行查询&#xff0c;再列出计划ActiveRecord::Base.connection.execute(EXPLAIN ANALYZE sql…

[论文笔记]P-tuning

引言 今天带来第四篇大模型微调的论文笔记GPT Understands, Too。 本篇工作提出的方法是P-tuning,使用可训练的连续提示嵌入,使GPT在NLU上表现比传统的全量微调的GPT更好的效果。P-tuning还提高了BERT在少样本和监督设定下的性能,大幅减少了提示工程的需求。 总体介绍 根…