字符串匹配—KMP算法

news2025/2/24 23:22:24

字符串匹配的应用非常广泛,例如在搜索引擎中,我们通过键入一些关键字就可以得到相关的搜索结果,搜索引擎在这个过程中就使用字符串匹配算法,它通过在资源中匹配关键字,最后给出符合条件的搜索结果。并且我们在使用计算机时,通常需要处理大量的字符串,例如编写C语言时的标准输入和标准输出都是字符串的形式,所以字符串匹配算法是一种非常重要的算法。

在介绍KMP算法之前,先简要说明一下暴力匹配算法。暴力匹配算法的思路非常简单,它通过将模式串与文本串中的子字符串(以文本串的第一个、第二个...以此类推直到第N+1-M个字符开始的长度为M的所有子串)进行比较,最后得到第一次匹配的位置。对于一个长度为N的文本串和长度为M的模式串,暴力匹配算法的时间复杂度为O(N*M).而KMP算法将时间复杂度减少到了O(N+M)

KMP算法是由D.EKnuth、J.H.Morris和V.R.Pratt提出的,时间复杂度为O(N+M)的字符串匹配算法。KMP算法之所以能将时间复杂度减少到这个量级,是因为在KMP算法中充分利用了已匹配成功的部分,使得文本串中的指针不需要像暴力匹配算法一样回溯到正在比较子串的开头(第二个字符,因为如果匹配失败,指针会移动到匹配失败子串的第一个字符的下一个字符并重新开始比较),在比较过程中,文本串的指针一直在向后移动,我们只需要回溯模式串的指针即可。

下面本文将用例子来详细说明KMP算法的思路及过程,假设我们要在文本串acacfacace中找到模式串acace(字符串末尾的\0字符省略)。

在开始之前,首先介绍两个概念,前缀和后缀。字符串的前缀是指不包括字符串最后一个字符的所有子串,对上述例子,a,ac,aca,acac...acacfacac是文本串的所有前缀,后缀就是不包括第一个字符的,从后往前的串,e,ce,ace...cacfacace是文本串的所有后缀。

acacfacace
acace

我们先开始从头匹配,匹配成功的部分标为红色,如上图。显然,模式串的最后一个字符与文本串的下一个字符不匹配,那么对于暴力匹配算法,接下来就会将文本串的指针回溯到第一个c字符,模式串的指针回溯到第一个字符。下面列出暴力匹配的一些步骤(用绿色表示接下来要匹配的串,注意标记的不是匹配成功的串,用紫色标记上一次匹配失败的位置):

acacfacace
acace

 那么可以看到,在f之前,cac与aca仍然不匹配,继续下一步:

acacfacace
acace

在这一步中,可以看到两个ac匹配成功了。那么我们仔细观察就会发现,在第一步中已经匹配成功了acac子串,对于下面两步的暴力匹配算法,我们仍然需要拿出第一步中已经匹配成功的文本串的子串的一部分来与模式串进行比较。

显然,文本串中已经匹配成功的算法一定是模式串的子串。那么我们就会发现上面两步暴力匹配算法就是多余的,因为我们不需要比较文本串和模式串,我们只需要考察模式串就会发现cac与aca是不匹配的,也就是说这一步的暴力匹配算法可以删除。

下面我们进一步观察已经匹配的模式串和接下来暴力匹配的部分有什么关系。在上面的例子中,如果我们将已匹配部分看成一个字符串,那么cac和aca就是它的一个后缀和前缀,ac和ac也是它的一个前缀和后缀,也就是说,在暴力匹配算法中的文本串指针还没有移出刚才比较成功的字符串时,文本串和模式串的每次比较实际上都是已匹配部分的前缀和后缀的比较。所以当我们找到模式串中已匹配部分的最长相等前后缀,那么将模式串的指针移动到这个前缀的下一个字符,文本串的指针不用移动,就可以继续比较,因为我们知道模式串指针之前的部分一定是匹配的,并且这个匹配部分一定是最长的。

我们需要建立一个next数组用来表示当模式串中一个位置的元素匹配失败时,模式串的指针应该回溯到什么地方重新比较。这个回溯位置取决于匹配失败元素之前的子串的最长相等前后缀。比如在上面的例子中,acac的最长相等前后缀的长度为2,也就是说,在下一次比较时,模式串的长度为2的前缀已经匹配成功了,所以只需要将指针移动到第三个字符即可。

KMP算法代码:

int KMP(char* t, char* s) {

	//t为文本串,s为模式串
	int sl = strlen(s);//模式串的长度
	int tl = strlen(t);//文本串的长度
	int i, j;

	//next数组,next数组标记每个模式串的字符匹配失败要返回的位置,所以元素的个数与模式串长度相同
	int* next = (int*)malloc(sizeof(int) * sl);
	if (next == NULL) {
		perror("malloc");
		return -1;
	}
	//建立next数组
	j = -1; i = 0;
	next[0] = -1;//如果不将next值设为-1,那么第一个元素匹配失败时就会回到它自己,这时候我们要将文本串的指针+1,
	//就需要单独的代码来完成这一部分,将next设为-1,就可以同时将两个指针都+1,这和匹配成功的逻辑是相同的
	while (i < sl) {//i是快指针,当它越界说明next建立结束
		//这里的代码逻辑是,建立next数组的过程实际上是模式串的子串中的前后缀匹配过程
		//i始终指向已处理的子串的最后一个元素,从i=1开始,如果i和j指向的元素相同,那么这两个元素刚好是
		//i=2元素的前缀和后缀,所以当i=2匹配失败时,将指针移动到j+1,因为0和1位置的元素已经匹配
		//可以看到i和j始终是同时向后移动的,也就是说,它们始终走过相同的距离,举一个特殊例子来说明这个next建立的原理
		//如果从j=0,i=1位置开始,i和j指向的元素始终相等,那么当i指向最后一个元素时,j指向倒数第二个元素
		//这时,从第一个字符到j-1,从第二个字符到i-1,它们分别是已匹配子串的最长相等前缀和后缀,那么当i匹配失败时
		//显然指针需要移动到j重新比较,这个特殊的例子就说明了i指向位置之前的子串的某个后缀始终是与0到j的前缀相等的
		//并且是最长的,因为只有满足一定的条件i和j才会同时+1
		if (j == -1 || s[i] == s[j]) {//将j的初始值设为-1是为了处理当始终不满足后面的条件时,将next的值设为0
			i++;
			j++;
			next[i] = j;
		}
		else {
			j = next[j];//当i和j位置的元素匹配失败时,由于i之前的元素都已经建立了next值,所以j进行回溯重新开始匹配
		}

	}
	//优化next数组
	//如果模式串中某个元素的next值指向的元素与它相同,那么这个元素匹配失败时,实际上不需要比较next值指向的元素
	//我们可以对next数组进行优化来避免这种情况
	//next[0]依然等于-1
	for (int i = 1; i < sl; i++) {
		if (s[i] == s[next[i]]) {//如果s[i]和该元素的next值指向的元素相同
			//那么将该元素的next值设置为其next值指向元素的next值,由于是从前往后处理的,所以新的next值指向的元素一定不等于它本身
			next[i] = next[next[i]];
		}
	}

	//KMP算法过程
	i = 0; j = 0;
	while (i < tl && j < sl) {
		if (j == -1 || t[i] == s[j]) {//如果需要从模式串的头开始匹配或者对应元素匹配成功
			i++;
			j++;
		}
		else {
			j = next[j];//否则回溯j
		}
	}

	if (j == sl) {
		return i - j;
	}
	return -1;
}

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

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

相关文章

SpringBoot解决用户重复提交订单(方式三:通过Redis实现-升级版)

文章目录前言1、方案实践1.1、引入Redis依赖1.2、添加Redis环境配置1.3、编写服务验证逻辑&#xff0c;通过 aop 代理方式实现1.4、在相关的业务接口上&#xff0c;增加SubmitLimit注解即可2、小结前言 在上一篇文章中&#xff0c;我们详细的介绍了随着下单流量逐渐上升&#…

【PyTorch】第二节:梯度的求解

作者&#x1f575;️‍♂️&#xff1a;让机器理解语言か 专栏&#x1f387;&#xff1a;PyTorch 描述&#x1f3a8;&#xff1a;PyTorch 是一个基于 Torch 的 Python 开源机器学习库。 寄语&#x1f493;&#xff1a;&#x1f43e;没有白走的路&#xff0c;每一步都算数&#…

python提取多个pdf特定页,并合并为新pdf文件

文章目录1&#xff0c;代码结构2&#xff0c;代码详解2.1&#xff0c;将范围字符串转成list2.2&#xff0c;获取pdf文件特定页2.3&#xff0c;将pdf页list合并为pdf文件并保存2.4&#xff0c;遍历所有要合并的文件&#xff0c;进行合并2.5&#xff0c;给出要合并的pdf文件及范围…

大模型学习

大模型学习计算机视觉方向ViTImage Token EmbeddingMulti-head Self-attentionStable Diffusionstable diffusion支持功能stable diffusion整体结构ClipText如何训练图像信息创建器&#xff08;Image information creator&#xff09;自动编码解码器&#xff08;降噪绘制图形&a…

One Note插件——gem for onenote的安装

文章目录一、前言二、报错原因三、解决方法一、前言 平时写笔记都是用的OneNote来记录&#xff0c;但是Onenote没有 Markdown编辑器 ,写起来很不方便&#xff0c;搜索了解后知道gem for OneNote这个插件&#xff0c;于是下载安装了&#xff0c;但是插件每次都要手动勾选&#…

什么是小程序SDK?安全吗?

前面分享了很多小程序相关的内容&#xff0c;常常提到小程序SDK的概念&#xff0c;但似乎有很多小伙伴不是很理解&#xff0c;今天就来跟大家聊聊小程序SDK。 什么是小程序SDK&#xff1f; 小程序SDK是一种开发工具包&#xff0c;用于开发和构建小程序应用程序。它提供了一系列…

【thingsboard+chirpstack 下行数据通信测试】

这里写目录标题 7. 节点未收到 tb 平台下发数据原因分析7.1 收到的size为07.2 节点收不到数据7.3 可以收到数据的一组例子7.4 节点没收到数据原因分析本文主要描述 tb 下发的数据,节点接收不到原因分析。 主要是数据格式以及解析脚本的对应关系 7. 节点未收到 tb 平台下发数据…

Golang数据类型比较

直接使用比较的情况 分类说明是否能比较说明基本类型整型&#xff08; int/uint/int8/uint8/int16/uint16/int32/uint32/int64/uint64/byte/rune等&#xff09;浮点数&#xff08; float32/float64&#xff09;复数类型&#xff08; complex64/complex128&#xff09;字符串&a…

《Vue3实战》 第一章 nods/npm安装、配置

1、nods.js安装&#xff08;Windows&#xff09; 1.1、下载并安装node https://nodejs.org/en/ , 安装到d盘nodejs目录 1.2、配置环境变量 path配置 1.3、配置全局包存放目录和缓存目录 在根目录下创建node_global&#xff08;全局包存放目录&#xff09;和node_cache&…

关于药物|新药|药品市场调研报告(实操资料分享)

药品市场调研报告是指对药品行业进行详细的市场情况研究和分析。往往伴随着药品市场调研目的地不同&#xff0c;如战略探索、新药开发、投资决策等&#xff0c;报告编辑的内容要点要求也不一样。但总的核心要点内容笔者已提炼&#xff0c;如下&#xff1a; 一、药品市场调研报告…

DeePMD-kit 配置环境备忘

版本 Conda Conda是一个开源的包管理系统和环境管理系统&#xff0c;用于安装多个版本的软件包及其依赖项&#xff0c;并在它们之间轻松切换。它可以在Linux、OS X和Windows上运行&#xff0c;是为Python程序创建的&#xff0c;但可以打包和分发任何软件。 conda enactivatec…

为何ChatGPT如此擅长编造故事?

“幻觉”——人工智能中的一个偏见性术语 AI聊天机器人(如OpenAI的ChatGPT)依赖于一种称为“大型语言模型”(LLM)的人工智能来生成它们的响应。LLM是一种计算机程序&#xff0c;经过数百万文本源的训练&#xff0c;可以阅读并生成“自然语言”文本语言&#xff0c;就像人类自然…

TCP报头结构和TCP协议特性

TCP报头结构 原端口号/目的端口号&#xff1a;表示数据是从哪个进程来&#xff0c;到哪个进程去&#xff1b; 32位序号/32位确认号&#xff1a;这个序号是取的发送方发送所用数据下一个字节的序号&#xff0c;发送方的序列号和接收方的确认号一样&#xff0c;才算接收成功&…

敏捷开发模式下如何用 PingCode 这类工具进行版本发布管理

在软件团队工作中&#xff0c;版本发布要达到好的发布效果&#xff0c;需要在版本发布前做好版本发布的规划&#xff0c;并对发布流程和进度进行管理 准备工作&#xff1a; 您已经创建了一个 PingCode 帐户【快速注册入口】 您创建了一个 PingCode Scrum或 Kanban 项目 您的…

【周末闲谈】文心一言,模仿还是超越?

个人主页&#xff1a;【&#x1f60a;个人主页】 系列专栏&#xff1a;【❤️周末闲谈】 周末闲谈 ✨第一周 二进制VS三进制 文章目录周末闲谈前言一、背景环境二、文心一言&#xff1f;(_)?三、文心一言的优势&#xff1f;&#x1f617;&#x1f617;&#x1f617;四、文心一…

使用 arm 架构实例搭建 Harbor

使用 arm 架构实例搭建 Harbor事情准备&#xff08;使用甲骨文云上实例时的准备事项&#xff09;第1步&#xff0c;准备自签名证书第2步&#xff0c;安装Docker-ce第3步&#xff0c;构建arm镜像第4步&#xff0c;安装Harbor第5步&#xff0c;访问Harbor第6步&#xff0c;上传镜…

TensorFlow 深度学习第二版:1~5

原文&#xff1a;Deep Learning with TensorFlow Second Edition 协议&#xff1a;CC BY-NC-SA 4.0 译者&#xff1a;飞龙 本文来自【ApacheCN 深度学习 译文集】&#xff0c;采用译后编辑&#xff08;MTPE&#xff09;流程来尽可能提升效率。 不要担心自己的形象&#xff0c;只…

2023年【第十四届蓝桥杯】省赛java b组填空题

第一题 令 S 1! 2! 3! ... 202320232023!&#xff0c;求 S 的末尾 9 位数字。 提示&#xff1a;答案首位不为 0。 考试时的想法以及题解&#xff1a; 如果我们直接按照题目描述直接来求每个阶乘和的话恐怕没有什么数据类型能够胜任&#xff0c;在考试时我一开始使用了…

Linux中的read/write和recv/send的区别,并使用recv/send实现简单的聊天功能

Linux中的read/write和recv/send的区别read/writeread/writeread/write的用法recv/sendrecv/sendrecv/send的用法LinuxLinuxLinux中的read/writeread/writeread/write和recv/sendrecv/sendrecv/send的区别下面是一个使用read/write进行文件读写操作的例子&#xff1a;下面是一个…

【云原生】Kubernetes(k8s)部署 MySQL+Dubbo+Nacos服务

一、说明二、部署 MySQL三、部署 Nacos四、部署 Dubbo 服务4.1. 创建镜像仓库的密钥4.2. 部署 provider 服务4.3. 部署 consumer 服务五、测试一、说明 本文介绍基于 Kubernetes(k8s) 环境集成阿里云 私有镜像仓库 来部署一套 Dubbo Nacos 的微服务系统&#xff0c;并使用 Ku…