字符串匹配 --- BF算法 KMP算法

news2024/12/23 20:59:05

 Welcome to 9ilk's Code World

       

(๑•́ ₃ •̀๑) 个人主页:       9ilk

(๑•́ ₃ •̀๑) 文章专栏:    算法Journey


本篇博客我们将介绍关于字符串匹配的BF算法以及KMP算法,请放心食用~


🏠 字符串匹配

假设有一个字符串为主串str,另一个字符串为子串sub,我们需要在str中找到sub出现的第一个位置,没有就返回-1表示找不到。

eg :  str = “abcde”   sub = “bcd”   ---> 子串在主串第一次出现的位置是1

🏠 BF算法

    BF算法也叫做“暴力匹配算法”,也就是在主串固定一个左端点left,定义一个右端点right,right一开始在left位置,让right遍历一次主串,子串也用一个变量cur遍历,没找到匹配的让left向前移动一位,cur回退到子串起始位置,right回退到新left位置再次遍历,直到left找到匹配的子串或者找不到的情况。

动画展示:

注意点:

  • 当子串为空串时没必要此时表示没找到
  • 当子串大于主串时无法进行匹配
  • 当开始匹配的位置不合理时无法进行匹配
  • 结束条件 : cur走完了子串找到了 / left走完了主串但cur没走完表示没走到

参考代码:

//BF算法
int BF(string & str, string& sub,int pos = 0)
{
	//判断位置合法性 以及主串子串合法性
	int lenstr = str.size();
	int lensub = sub.size();
	if (lensub > lenstr || lensub == 0)
		return -1;
	if (pos < 0 || pos >= lenstr)
		return -1;
	int left = pos; 
	int right = left; //遍历主串
	int cur = 0;
	while (cur < lensub && left < lenstr)
	{
		if (str[right] == sub[cur])
		{
			cur++;
			right++;
		}
		else
		{
			cur = 0;
			left++;
			right = left;
		}
	}
	//终止条件
	if (cur >= lensub)
	{
		return left;
	}
	return -1;
}

时间复杂度 : O(m*n) m为主串长度,n为子串长度


🏠 KMP算法

    📒 引入

在了解完BF算法后,对于此时的不匹配,我们很自然的会想要把left向前挪,再从头逐个比较。这样做虽然可行,但是效率很差,因为你要把"搜索位置"移到已经比较过的位置,重比一遍。

那有什么改进效率的方法呢?

📒 什么是KMP

对于此时不匹配我们能提取到的一个信息是你其实知道前面六个字符是"ABCDAB",因此我们可以利用这个信息找到最大程度能匹配子串的一部分,此时就是最好的情况

这便是KMP算法的核心 ---  利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。与BF算法不同的是,当匹配失败时,主串时不回退的,子串回退到相应位置。

问题是KMP算法中,当匹配失败时,子串怎么知道该回退到哪个位置?

📒 next数组

所谓next数组,保存的是子串中某个位置匹配失败后应该回退到的位置

  • 规律1

对于这对字符串匹配,当在i位置匹配失败时,为了能够在主串中尽量找到和子串匹配的一部分,此时j最好回退到2号下标,而我们发现子串中[0,1]和[3,4]这两个相同的部分长度正好是2

因此找j所要回退的位置关键是去找在主串匹配成功的部分里面取去找到一部分子串1(比如上面子串的【3,4】)和我们子串里面一部分串是相同的(比如子串里面的【0,1】和【3,4】)

✏️ 手动求next数组

next数组:next[j]=k;,不同的j来对应一个K值,这个K就是你将来要移动的j要移动的位置。

1、规则:找到匹配成功部分的两个相等的真子串(不包含本身),一个个以下标0开始,另一个以j-1下标结尾。
2、不管什么数据
next[0]=-1;next[1]=0;在这里,我们以下标来开始,而说到的第几个第几个是从1开始;

示例1 :

对于下标7的位置,它前面对应的【0,1】和【5,6】区间是两个相等的真子串,因此当在7下标匹配失败时,j应该回退到2号位置,也就是next[7] = 2;

示例2 :

对于下标10的位置,它前面【0,6】和【3,9】区间是两个相等的真子串,因此此时next[10]  = 7;

练习:

  • 对于"ababcabcdabcde",求其的next数组?
  • 再对"abcabcabcabcdabcde",求其的next数组?"

答案 :

1. -10012012001200

2. -100012345678901230

小tips : 每个next数组中next[i+1]如果是大于next[i],一定有next[i + 1] = next [i] + 1; 

✏️ 已知next[i] 如何求 next[i+1] 

前提 : 假设 next [ i ] = k;

  • p[i] = p[k]时(比如下面字符串中下标8对应字符等于下标3对应字符)

我们假设x为第二个真子串起始位置,注意x的位置不一定就是k

因此我们可以得到 k - 1 - 0 = i - 1 - x;

--> x = i - k;  k = i - x;

--->p[0]...p[x] == p[0]...p[k-1] == p[i-k]...p[i-1] (1)

又由于 p[i] == p[k]  (2)

由(1)(2)式得 : p[0]...p[k-1][k] == p[i-k]...p[i-1]p[i] 

---> p[0]...p[k] == p[i-k]...p[i]   (3)

由数学归纳法可知: next[i] = k 对应 (1)式,同理next[i+1] = k + 1对应(3)式

  • p[i] != p[k]

我们可以发现此时next[i+1] != k+1 ,也就是k回退到的2号位置不是你需要的,那怎么办呢?此时我们需要继续回退到2号对应next数组的值,此时k回退到了0下标且此时p[i] = p[k],k+1正好等于next[i+1],即next[6]

因此 如果p[i] != p[k] ,此时我们需要一直回退,回退到满足p[i] == p[k]的情况.

📒 简单实现KMP算法

  • 获得next数组

整体思路是基于我们推导的next[i] 求 next[i+1]分为p[i] == p[k] 和 p[i] != p[k]两种情况,由于下标0和1我们已知(规定了是-1和0,next[0] = -1,next[1] = 0),因此我们在求next[2]时我们可以利用next[1],同理next[3]可以利用next[2]... 因此我们next[i]的求法就是去利用next[i-1].

注: 当p[i] != p[k]时,我们的k可能一直回退到-1此时我们把他归类到p[i] == p[k]的情况直接让此时的next[i] = k + 1;

参考代码 : 

void GetNext(vector<int>& next,string& sub)
{
	if (sub.size() >= 1)
	{
		next[0] = -1;
	}
	next[1] = 0;
	int k = 0;//指的是前一项的k
	int i = 2; //从下标为2开始
	while (i < sub.size())
	{
		if (k == -1 || (sub[i - 1] == sub[k])) // p [i] == p [k]
		{
			next[i] = k + 1;
			i++;
			k++;
		}
		else
		{
			k = next[k]; // p[i] != p[k] 回退到满足 p[i] == p[k]
		}
	}

}
  • KMP算法整体逻辑

核心步骤:

1. 获取next数组

2. 匹配失败时,根据next数组的信息快速匹配

⚠️:主串和子串和合法性以及开始匹配位置的合法性

参考代码:

int KMP(string& str, string& sub, int pos = 0)
{
	int lenstr = str.size();
	int lensub = sub.size();
	if (pos < 0 || pos >= lenstr)
		return -1;
	if (lensub > lenstr || lensub == 0)
		return -1;
	int i = pos; //遍历主串
	int j = 0; // 遍历子串
	//获取next数组
	vector<int> next;
	next.resize(lensub);
	GetNext(next,sub);
	while (i < lenstr && j < lensub)
	{
		if (j == -1 || str[i] == sub[j]) // 注意j可能根据匹配信息回退到-1
		{
			i++;
			j++;
		}
		else
		{
			j = next[j]; //匹配失败要回退的位置
		}
	}
	if (j >= lensub)
		return i - j;
	else
		return -1;

}

本博客主要是简单的科普字符串算法,文章设计的数学知识可能不严谨欢迎指正!

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

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

相关文章

【大模型】大模型微调方法总结(三)

1. Prefix-tuning 1.背景 2021年论文《Prefix-Tuning: Optimizing Continuous Prompts for Generation》中提出了 Prefix Tuning 方法。与Full-finetuning 更新所有参数的方式不同&#xff0c;该方法是在输入 token 之前构造一段任务相关的 virtual tokens 作为 Prefix&#x…

【数据结构】带头双向循环链表的实现及链表顺序表的区别

目录 一、带头双向循环链表接口实现 连接关系&#xff1a; 创建哨兵位&#xff08;表头&#xff09;&#xff1a; 头插——头删&#xff1a; 尾插——尾删&#xff1a; 查找——打印&#xff1a; 指定位置pos前插入&#xff0c;删除pos位置&#xff1a; 链表销毁&#x…

springboot解析自定义yml文件

背景 公司产品微服务架构下有十几个模块&#xff0c;几乎大部分模块都要连接redis。每次在客户那里部署应用&#xff0c;都要改十几遍配置&#xff0c;太痛苦了。当然可以用nacos配置中心的功能&#xff0c;配置公共参数。不过我是喜欢在应用级别上解决问题&#xff0c;因为并不…

“管式加热炉简单控制系统和串级控制系统设计与Matlab仿真”,高分资源,匠心制作,下载可用。强烈推荐!!!

“管式加热炉简单控制系统和串级控制系统设计与Matlab仿真”毕业设计&#xff0c;高分资源&#xff0c;匠心制作&#xff0c;下载可用。强烈推荐&#xff01;&#xff01;&#xff01; 1.控制目标 加热炉的任务是把原油加热到一定温度&#xff0c;以保证下道工艺的顺利进行。…

【C语言】解决C语言报错:Double Free

文章目录 简介什么是Double FreeDouble Free的常见原因如何检测和调试Double Free解决Double Free的最佳实践详细实例解析示例1&#xff1a;重复调用free函数示例2&#xff1a;多次释放全局变量指针示例3&#xff1a;函数间传递和释放指针 进一步阅读和参考资料总结 简介 Doub…

基于redisson实现tomcat集群session共享

目录 1、环境 2、修改server.xml 3、修改context.xml 4、新增redisson配置文件 5、下载并复制2个Jar包到Tomcat Lib目录中 6、 安装redis 7、配置nginx负载均衡 8、配置测试页面 9、session共享测试验证 前言&#xff1a; 上篇中&#xff0c;Tomcat session复制及ses…

关于等保测评你了解多少?

在当今数字化时代&#xff0c;网络安全问题愈发凸显其重要性。作为保障信息系统安全的关键环节&#xff0c;等保测评&#xff08;网络安全等级保护测评&#xff09;成为了企业和组织不可或缺的一部分。那么&#xff0c;关于等保测评&#xff0c;我们究竟了解多少呢&#xff1f;…

基于STM32的智能家庭安防系统

目录 引言环境准备智能家庭安防系统基础代码实现&#xff1a;实现智能家庭安防系统 4.1 数据采集模块4.2 数据处理与分析4.3 控制系统实现4.4 用户界面与数据可视化应用场景&#xff1a;家庭安防管理与优化问题解决方案与优化收尾与总结 1. 引言 智能家庭安防系统通过使用ST…

鸿蒙系统最简单安装谷歌服务及软件的方法

哈喽&#xff0c;各位小伙伴们好&#xff0c;我是给大家带来各类黑科技与前沿资讯的小武。 近日&#xff0c;华为开发者大会在东莞松山湖召开&#xff0c;发布了盘古大模型5.0和纯血版的鸿蒙 HarmonyOS NEXT 全场景智能操作系统&#xff0c;而根据研究机构 Counterpoint Resea…

MySQL进阶_3.MySQL日志

文章目录 第一节、MySQL事务日志1.1、redo日志1.1.1、为什么需要REDO日志1.1.2、REDO日志的好处、特点1.1.3、redo的组成1.1.4、redo的整体流程 1.2、Undo日志1.2.1、如何理解Undo日志1.2.2、Undo日志的作用1.2.3、undo log的生命周期 第一节、MySQL事务日志 事务有4种特性&am…

按位与、或、异或操作符

目录 & --- 按位与操作符 按位与操作符运用规则 按位与操作符相关代码 按位与操作符相关代码验证 | --- 按位或操作符 按位或操作符运用规则 按位或操作符相关代码 按位或操作符相关代码验证 ^ --- 按位异或操作符 按位异或操作符运用规则 按位异或操作符相关代…

24/06/26(1.1129)动态内存

strtok 字符串分割函数 #include<stdio.h> int main(){ char str[] "this,a sample string."; char* sep ","; char* pch strtok(str, sep); printf("%s\n", pch); while (pch ! NULL){ printf("%s\…

LabVIEW中卡尔曼滤波的作用与意义

卡尔曼滤波&#xff08;Kalman Filter&#xff09;是一种在控制系统和信号处理领域广泛应用的递推滤波算法&#xff0c;能够在噪声环境下对动态系统的状态进行最优估计。其广泛应用于导航、目标跟踪、图像处理、经济预测等多个领域。本文将详细介绍卡尔曼滤波在LabVIEW中的作用…

[JS]对象

介绍 对象是一种无序的数据集合, 可以详细的描述某个事物 事物的特征在对象中用属性来表示, 事物的行为在对象中用方法来表示 使用 创建对象 let 对象名 {属性名&#xff1a;值&#xff0c;方法名&#xff1a;函数&#xff0c; } let 对象名 new Object(); 对象名.属性…

深入了解 msvcr120.dll问题解决指南,msvcr120.dll在电脑中的重要性

在Windows操作系统中&#xff0c;.dll 文件扮演了非常重要的角色&#xff0c;它们包含许多程序运行所需的代码和数据。其中 msvcr120.dll 是一个常见的动态链接库文件&#xff0c;是 Microsoft Visual C Redistributable Packages 的一部分。这篇文章将探讨 msvcr120.dll 的功能…

Profibus DP主站转Modbus模块连接马达保护器案例

一、概述 在工业自动化控制系统中&#xff0c;Profibus DP和Modbus是常见的通信协议&#xff0c;在同一现场还有可能遇到Modbus协议&#xff0c;ModbusTCP协议&#xff0c;Profinet协议&#xff0c;Profibus协议&#xff0c;Profibus DP协议&#xff0c;EtherCAT协议&#xff…

appinventor2中求某个值在列表中的索引用什么方法?

使用“求对象在列表中的位置”方法就可以了&#xff1a; 返回指定对象在列表中的位置&#xff0c;从 1 开始&#xff0c;如果不在列表中&#xff0c;则返回 0。 相应地&#xff0c;知道了索引&#xff0c;从列表中取值得方法是&#xff1a;选择列表中索引值对应的列表项 返回…

条码二维码读取设备在医疗设备自助服务的重要性

医疗数字信息化建设的深入推进&#xff0c;医疗设备自助服务系统已成为医疗服务领域的一大趋势&#xff0c;条码二维码读取设备作为自助设备的重要组成部分&#xff0c;通过快速、准确地读取条形码二维码信息&#xff0c;不公提升了医疗服务效率&#xff0c;还为患者提供了更加…

教你如何一键高效下载视频号直播视频

在当今视频号直播盛行的时代&#xff0c;错过精彩直播内容再也不是遗憾&#xff01;地瓜网络技术倾情推出“视频号直播视频下载器”&#xff0c;为您捕捉每一个直播瞬间。本文将简明扼要地指导您如何利用这款神器下载视频号直播与回放视频&#xff0c;让超清MP4视频轻松入库&am…

遇到不可复现的bug要怎么做?

测试人员遇到不可复现的bug要怎么做&#xff1f; 这是一个很常见的问题&#xff0c;也是一个很棘手的问题。不可复现的bug可能会给测试人员带来很大的困扰和压力&#xff0c;因为它们可能会影响软件的质量和用户的体验&#xff0c;但又很难找到问题的根源和解决方法。因此&…