11-KMP算法

news2024/12/31 5:06:35

KMP算法是一个字符串匹配算法,总的意义是在给定的字符串A中利用优化的方法快速地找出字符串B的位置,相比于传统匹配算法,它能有效减少匹配时间,提高效率。

前缀和后缀

在我们看KMP算法前我们先考虑一个问题:假如我们想要在字符串s里面检索其是否包含了某个字串p。例如字符串s是“abcdabcdf”,字符串p为“dab”,那么按照我们之前的理解,可以从字符串s第一个字符开始,和字符串p中每一个字符进行逐项对比。如果有出入,需要从字符串s的下一个元素开始又重新与字符串p中的每个元素逐项对比。

#include<iostream>

using namespace std;

const int N = 10, M = 4;

int main()
{
	char s[N] = "abcdabcdf";
	char p[M] = "dab";
	for (int i = 0; i < N-1; i++)
	{
		int flag = 1;
		int tmp = i;
		for (int j = 0; j < M-1; j++)
		{
			if (s[tmp++] != p[j])
			{
				flag = 0;
				break;
			}
		}
		if (flag == 1)
		{
			cout <<"下标为:" << i << endl;
			break;
		}
	}
	return 0;
}

而正是因为上面这种排序方式很耗费时间,所以KMP字符串算法诞生了。KMP算法是一种典型的以空间换时间的字符串匹配算法。在了解KMP之前,我们需要知道两个概念:一个字符串的相等前缀后缀。举个例子:有一个字符串”dabbcda“,前缀是一个字符串字串,它是指从第一个元素开始到后面(除结尾外)的任意一个元素截止中间的字符串。例如前面字符串的前缀就有(d、da、dab、dabb、dabbc、dabbcd);类似地,它的后缀是指以最后一个元素为截止,向前(除开头元素)的任意元素为起始的中间字符串。前面字符串的后缀就有(a、da、cda、bcda、bbcda、abbcda)。而相等前缀\后缀就是前缀和后缀共有的字符串,前面的字符串的相等前缀\后缀就是字符串字串da。

KMP算法匹配原理

现在我们看看在一次匹配过程中利用KMP算法的匹配过程:例如我们有一个字符串s(acabdabeac)和p(abdabd),我们需要在s里面找出是否有p,若有则返回该字串p在字符串s完全匹配后的首元素下标。

一开始还是照常用p中的每一个字符和s字符串中的每一个字符进行对比,比不上则p继续向s字符串的下一个字符的字串进行对比,注意在匹配到两个及以上的元素时KMP的优势就可以显现出来了。

直至上面我们执行到第三遍时,字符串p中的前面5个元素都和字符串s中间的几个元素对上了,但是到了最后一个元素却对不上了,这时候KMP的处理方式就不同与之前我们传统的处理方式。传统的处理方式是下一次重新将字符串p中的a和字符串s中的b进行对比:

而KMP算法的处理方式不同,当s字符串仅匹配到局部的p字符串,以p字符串中第一个与s字符串相异的元素左边的元素集合为子串,这个子串的最大相等前缀的首元素与之前和其最大相等后缀第一个元素比较的那个s字符串中的元素再对齐(即子串p的前缀镦到其后缀的位置)。以下面的例子为例:第三次匹配时p字符串前面5个字符都匹配上了,唯独后面的d匹配不上,这时候利用KMP算法,字符串p以d为分界,左边字串最大相等前缀/后缀为ab,所以字符串p的前缀将会镦到其后缀的地方。

并且将会从其前缀后的第一个元素m开始和字符串s对应的元素开始逐项对比,而不是回溯到字符串p的第一个元素。而这个元素m的位置可以由next数组确定。next数组存储着p字符串中每个元素所对应子串的最大相等前缀/后缀的长度,而这个开始对比的元素的位置就是p[next[2](next数组存储的第三个元素,d元素对应的最大前缀/后缀长度)]

next数组

next数组是KMP算法中存储模式串(上文中字符串p)中以某一个元素为结束的子串的最大相等前缀/后缀的长度。举个例子:字符串abcdabef。以a为结束的子串为a,它的最大相等前缀/后缀为0,相似地,可以算出以b为结束的子串最大相等前缀/后缀为0.....,而next数组里面对应每一个位存储的就是模式串中以对应位为结束的字串的最大相等前缀/后缀的长度。它们的关系如下表所示:

结束的元素子串最大相等前缀/后缀长度next数组的存储
aa0next[0]=0
bab0next[1]=0
cabc0next[2]=0
dabcd0next[3]=0
aabcda1next[4]=1
babcdab2next[5]=2
eabcdabe0next[6]=0
fabcdabef0next[7]=0

知道了next数组的作用,我们再来看看如何用代码将next数组实现:首先我们来看看总代码:

	const int Smax = 100, Pmax = 10;

    char s[Smax],p[Pmax];
    int s_len, p_len;
    int ne[Pmax];


    for (int i = 1, j = 0; i < p_len; i++)
	{
		while (j && p[i] != p[j])
			j = ne[j];
		if (p[i] == p[j])
			j++;
		ne[i] = j;
	}

这里的 i 表示字符串p的元素下标,我们需要逐个求出字符串p每一项元素对应子串的最大相等前缀/后缀的长度。这里我们使用到的是一个for循环来对字符串p的每个元素进行计算,我们从下标1开始,因为任何一个字符串的第一个元素的最大相等前缀/后缀的长度肯定是0。j是最大相等前缀/后缀的长度。为了理解for循环里面的内容,我们假设一个场景:目前字符串p匹配到下标为 i 的元素,那么要去求的目前这个元素的最大前缀/后缀就需要在之前已经求得的j的基础上匹配p[i]和p[j]的元素。如果相同,那么最大前缀/后缀就加一即对应j++。

如果不匹配,那么进入while循环,这个循环的意义在于在之前已经找到的最大前缀/后缀的基础上寻找一个更小的最大前缀/后缀(逐渐缩小最大前缀/后缀的过程),下图蓝色部分。

了解了next数组后,我们可以发现实现KMP算法的流程和实现next数组类似,只不过是之前是模式串中的前缀最后一个元素和后缀最后一个元素的对比,这里是s字符串中元素和p字符串中元素的对比。当我们j对比到最后一个元素时,对比上了,那么j会加加变成p的长度,那就代表成功了。这时候就输出当前 i 的位置。记住 i 的头位置是在减去p的长度后加一的位置。

所以匹配代码是:

    //kmp匹配过程
	for (int i = 0, j = 0; i < s_len; i++)
	{
		while (j && s[i] != p[j])
			j = ne[j];
		if (s[i] == p[j])
			j++;
		if (j == p_len)
		{
			cout << i - p_len+1<<" ";
			j = ne[j];
		}

总代码

#include<iostream>
using namespace std;

const int Smax = 100, Pmax = 10;

char s[Smax],p[Pmax];
int s_len, p_len;
int ne[Pmax];


int main()
{
	cin >> s_len >> s  >> p_len >> p ;
	//next数组求法
	for (int i = 1, j = 0; i < p_len; i++)
	{
		while (j && p[i] != p[j])
			j = ne[j];
		if (p[i] == p[j])
			j++;
		ne[i] = j;
	}
	//kmp匹配过程
	for (int i = 0, j = 0; i < s_len; i++)
	{
		while (j && s[i] != p[j])
			j = ne[j];
		if (s[i] == p[j])
			j++;
		if (j == p_len)
		{
			cout << i - p_len + 1 <<" ";
			j = ne[j];
		}
	}
	return 0;
}

上面是我学习KMP算法的笔记,因为我对KMP算法理解可能还不够深入,所以可能对某些地方的叙述有些问题或者哪里有错误,希望大家能够批评指正。 



参考资料:

参考资料1

参考资料2

参考资料3

参考资料4

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

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

相关文章

基于框架的平台总线式开发

一、总线、设备、驱动 硬编码式的驱动开发带来的问题&#xff1a; 1. 垃圾代码太多 2. 结构不清晰 3. 一些统一设备功能难以支持 4. 开发效率低下 1.1 初期解决思路&#xff1a;设备和驱动分离 struct device来表示一个具体设备&#xff0c;主要提供具体设备相关的资源&am…

Telnet 基础实验2: SSH 实验

Telnet 基础实验2&#xff1a; SSH 实验 本实验只能使用 eNSP中 AR 系统的路由器做 拓扑图 SSH &#xff1a; Secure Shell 是一个网络安全协议&#xff0c;基本于 TCP 协议 22 端口传输数据&#xff0c;通过对网络数据的加密&#xff0c;使其能够在一个不安全的网络环境中&a…

网易新财报:游戏养家,教育维稳、音乐快走

配图来自Canva可画 随着互联网流量红利逐渐消退&#xff0c;互联网大厂们也告别高增长时代&#xff0c;逐渐进入稳定增长阶段。近两年&#xff0c;流量焦虑、业务失速等问题更是成为了一团浓雾&#xff0c;笼罩在互联网大厂周围。不过&#xff0c;面对所遭遇的难题&#xff0c…

力扣-换座位

大家好&#xff0c;我是空空star&#xff0c;本篇带大家了解一道简单的力扣sql练习题。 文章目录前言一、题目&#xff1a;626. 换座位二、解题1.正确示范①提交SQL运行结果2.正确示范②提交SQL运行结果3.正确示范③提交SQL运行结果4.正确示范④提交SQL运行结果5.其他总结前言 …

读书笔记——《富爸爸穷爸爸》

《富爸爸穷爸爸》&#xff0c;以前不屑读这种书。这种书就是那种走进书店放在门口展销位的成功学著作&#xff0c;一眼看上去没什么实在的内容&#xff0c;看上去很不靠谱&#xff0c;感觉就是骗一些社会底层又做着暴富梦的人来买的&#xff0c;但是由于自身原因或环境局限根本…

Spring Boot + Vue3 前后端分离 实战 wiki 知识库系统<二>---后端架构完善与接口开发

数据库准备&#xff1a; 在上一次Spring Boot Vue3 前后端分离 实战 wiki 知识库系统<一>---Spring Boot项目搭建已经将SpringBoot相关的配置环境给搭建好了&#xff0c;接下来则需要为咱们的项目创建一个数据库。 1、mysql的安装&#xff1a; 关于mysql的安装这里就…

【C语言每日一题】杨氏矩阵(源码以及改进源码)

【C语言每日一题】—— 杨氏矩阵&#x1f60e;&#x1f60e;&#x1f60e; 目录 &#x1f4a1;前言&#x1f31e;&#xff1a; &#x1f49b;杨氏矩阵题目&#x1f49b; &#x1f4aa; 解题思路的分享&#x1f4aa; &#x1f60a;题目源码的分享&#x1f60a; &#x1f4…

夜天之书 #73 Apache Pulsar 的社群指标

去年十一月&#xff0c;我成为了 Apache Pulsar[1] 社群 Committers 的一员。成为 Committer 之前和之后&#xff0c;我都积极参与了代码仓库上 Issue 和 Pull Request (PR) 的处理回应和评审。去年十二月期间&#xff0c;我把未解决的 Issue 和 PR 数量分别从接近 2000 个和 4…

STM32学习笔记-I2C通信协议

文章目录介绍&#xff1a;两种实现方式&#xff1a;I2C设备的常用连接方式&#xff1a;I2C协议时序&#xff1a;STM32硬件I2C框架图I2C外设通讯过程**I2C读写EEPROM**&#xff08;硬件I2C&#xff09;介绍&#xff1a; 两根通信线SCL&#xff08;时钟线&#xff09;、SDA&#…

C语言中的强制类型转换

强制类型转换是把变量从一种类型转换为另一种数据类型。例如&#xff0c;如果您想存储一个 long 类型的值到一个简单的整型中&#xff0c;您需要把 long 类型强制转换为 int 类型。您可以使用强制类型转换运算符来把值显式地从一种类型转换为另一种类型&#xff0c;如下所示&am…

“ChatGPT之父”Sam Altman:如何成功?

背靠微软&#xff0c;OpenAI能拳打谷歌&#xff0c;脚踢Meta&#xff0c;它背后的男人&#xff0c;必然不简单。 让我们来看一看&#xff0c;Sam Altman是如何一步步成长为今天这个搅动全世界的男人。 山姆奥特曼&#xff08;Sam Altman&#xff09; 成长和创业经历 在YC创始…

代码随想录【Day27】| 39. 组合总和、40. 组合总和 II、131. 分割回文串

39. 组合总和 题目链接 题目描述&#xff1a; 给定一个无重复元素的数组 candidates 和一个目标数 target &#xff0c;找出 candidates 中所有可以使数字和为 target 的组合。 candidates 中的数字可以无限制重复被选取。 说明&#xff1a; 所有数字&#xff08;包括 tar…

JavaScript 高级2 :构造函数和原型 d331702016e84f54b3594ae05e0eeac

JavaScript 高级2 &#xff1a;构造函数和原型 Date: January 16, 2023 Text: 构造函数和原型、继承、ES5中的新增方法 目标 能够使用构造函数创建对象 能够说出原型的作用 能够说出访问对象成员的规则 能够使用 ES5新增的一些方法 构造函数和原型 概述 在典型的 OOP 的…

MySQL之EXPLAIN

使用方法 查询结果分析 id&#xff1a;识别符 select_type&#xff1a;表示查询的类型 table&#xff1a;输出结果集的表 partitions&#xff1a;匹配的分区 type&#xff1a;表示表的连接类型 possible_keys&#xff1a;表示查询时&#xff0c;可能使用的索引 key&#xff1a…

jni-Demo-基于linux(c++ java)

跑一个jni 的最简单的Demo需要提前准备 VsCode 编译器、win10下&#xff0c;vscode中集成linux操作系统、c编译器&#xff08;gcc、g&#xff09;&#xff0c;java编译器&#xff08;jdk1.8&#xff09;参考&#xff1a;https://mangocool.com/1653030123842.htmlJniDemo类&…

【分享】灌溉制度设计小程序VB源代码

说明 根据作物需水特性和当地气候、土壤、农业技术及灌水技术等因素制定的灌水方案。主要内容包括灌水次数、灌水时间、灌水定额和灌溉定额。灌溉制度是规划、设计灌溉工程和进行灌区运行管理的基本资料&#xff0c;是编制和执行灌区用水计划的重要依据。 1—计划湿润土层允…

spring面试题总结

1、spring是什么&#xff1f; spring是一个轻量级IOC和AOP容器框架&#xff0c;是为Java应用程序提供基础性服务的一套框架&#xff0c;目的是用于简化企业应用的开发&#xff0c;开发者只需要关注业务需求即可&#xff1a; core container 容器组件 spring context&#xff0c…

@ConfigurationProperties在方法上的使用

文章目录1. 前言2. 先说结论3. 代码解释1. Component ConfigurationProperties2. EnableConfigurationProperties ConfigurationProperties3. Bean ConfigurationProperties1. 前言 在学习spring的时候&#xff0c;ConfigurationProperties应该经常被使用到&#xff0c;作用…

一文弄清混合云架构模式

当我们在说云架构的时候&#xff0c;通常指的并不是云平台的自身架构&#xff0c;而是基于云平台的软件系统基础架构。云平台的自身架构满足了很多通用层面的需求&#xff0c;例如对象存储&#xff0c;弹性主机&#xff0c;虚拟网络等等&#xff0c;只有云服务厂商的工程师才会…

Win10系统内置杀毒软件Windows Defender卸载方法

Windows10系统自带的Windows Defender杀毒软件&#xff0c;偶尔会使你的电脑CPU满负荷运载。 其实我们完全可以删除这个Windows10自带的杀毒软件&#xff0c;我们忍耐总是有限度的&#xff0c;所以&#xff0c;你可以选择删除它&#xff1b; 怎么删除呢&#xff1f;下面有个方…