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

 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;

}

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

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

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

相关文章

算法07 深度优先搜索及相关问题详解

深搜与广搜是搜索算法中最常用的两种算法&#xff0c;通过深度优先搜索解决问题还会用到回溯和剪枝&#xff0c;让我们一起进入本章&#xff0c;了解深搜的基本概念和模板&#xff0c;并学会解决一些常见问题。 目录 问题导入 走迷宫问题 如何走&#xff1f; 问题建模 如何…

(2024,频域 LoRA,DFT,DCT,自适应门控,基于适配器组合的图像编辑)FouRA:傅里叶 LoRA

FouRA: Fourier Low Rank Adaptation 公和众与号&#xff1a;EDPJ&#xff08;进 Q 交流群&#xff1a;922230617 或加 VX&#xff1a;CV_EDPJ 进 V 交流群&#xff09; 目录 0. 摘要 2. 相关工作 3. 提出的方法 3.1 低秩适应的公式 3.2 频域中的低秩适应 3.3 频率变换 …

【个人博客搭建】(26)发布后端webapi项目

1、选择启动的webapi&#xff0c;右击发布 2、选择左下角的“显示所有设置” 在上一页按钮那边是发布文件夹的目录 地址&#xff0c; 现在界面的就是配置的信息&#xff0c; 配置&#xff1a;Debug、Release 目标框架&#xff1a;我们用的net8.0&#xff0c;就是他&#xff…

2.移植freertos到stm32f103c8t6

目录 1.步骤 2.freertos配置时常见的选项卡意思 1.步骤 2.freertos配置时常见的选项卡意思

Michael.W基于Foundry精读Openzeppelin第60期——Clones.sol

Michael.W基于Foundry精读Openzeppelin第60期——Clones.sol 0. 版本0.1 Clones.sol 1. 目标合约2. 代码精读2.1 clone(address implementation) internal2.2 cloneDeterministic(address implementation, bytes32 salt)2.3 predictDeterministicAddress(address implementatio…

Upload-Labs-Linux1 使用 一句话木马

解题步骤&#xff1a; 1.新建一个php文件&#xff0c;编写内容&#xff1a; <?php eval($_REQUEST[123]) ?> 2.将编写好的php文件上传&#xff0c;但是发现被阻止&#xff0c;网站只能上传图片文件。 3.解决方法&#xff1a; 将php文件改为图片文件&#xff08;例…

小程序开发平台源码系统——社区团购小程序功能 前后端分离 带完整的安装代码包以及搭建教程

系统概述 在当今数字化时代&#xff0c;社区团购已经成为一种热门的购物模式。为了满足市场需求&#xff0c;拥有一个功能强大的社区团购小程序是至关重要的。本文将深入探讨一款具备前后端分离特性的小程序开发平台源码系统&#xff0c;着重介绍其社区团购小程序功能&#xf…

CleanMyMac2024免费版下载!轻松清理垃圾文件、优化系统性能

亲爱的小伙伴们&#xff5e;&#x1f44b;今天我要给大家分享一款神奇的软件&#xff0c;它就是 CleanMyMac 2024 免费版&#xff01;这款软件不仅能帮你轻松清理垃圾文件、优化系统性能&#xff0c;还有更多惊喜功能等你来探索哦&#xff01;&#x1f38a; CleanMyMac绿色免费…

第三十二篇——大数据2:大数据思维的四个层次

目录 一、背景介绍二、思路&方案三、过程1.思维导图2.文章中经典的句子理解3.学习之后对于投资市场的理解4.通过这篇文章结合我知道的东西我能想到什么&#xff1f; 四、总结五、升华 一、背景介绍 我们生活在这个时代&#xff0c;我们是否按照这个时代需要的思维方式去思…

【Linux】使用chrony同步时间

chrony介绍 chrony 是一个开源的网络时间协议 (NTP) 客户端和服务器&#xff0c;旨在保持计算机系统的时间精确同步。它是Linux和其他类Unix系统中广泛使用的工具&#xff0c;特别是在需要高精度时间同步的环境中。chrony 的设计考虑了现代网络的挑战&#xff0c;如不稳定的连…

MyPostMan:按照项目管理接口,基于迭代生成接口文档、执行接口自动化联合测试

MyPostMan 是一款类似 PostMan 的接口请求软件&#xff0c;不同于 PostMan 的是&#xff0c;它按照 项目&#xff08;微服务&#xff09;、目录来管理我们的接口&#xff0c;基于迭代来管理我们的接口文档&#xff0c;可导出或者在局域网内共享&#xff0c;按照迭代编写自动化测…

数据结构与算法笔记:高级篇 - 概率统计:如何利用朴素贝叶斯算法过滤垃圾短信?

概述 上篇文章我们讲到&#xff0c;如何用位图、布隆过滤器&#xff0c;来过滤重复数据。本章&#xff0c;我们再讲一个跟过滤相关的问题&#xff0c;如果过滤垃圾短信&#xff1f; 垃圾短信和骚扰电话&#xff0c;我想每个人都收到过吧&#xff1f;买房、贷款、投资理财、开…

带你学习PID算法2

#PID讲解 前言&#xff1a;本文参考华南小虎队的PID视频&#xff0c;视频连接放在最后 下图工人控制水阀可以满足&#xff1a; 1流量稳定 2随时改变流量 如果预期流量是1L/s&#xff0c;实际流量确实0.8L/s&#xff0c;工人就会调节阀门&#xff0c;使其达到&#xff0…

论文学习_基于导向式模糊测试的二进制程序漏洞验证方法

1. 引言 研究背景及现存问题:基于代码相似性比较的漏洞检测方法属于静态分析方法,不可避免地存在误报率高的问题,对静态检测方法得到的疑似漏洞代码进行人工分析存在工作量大, 效率低的问题。解决该问题的有效的方案之一是使用导向式模糊测试方法,生成能够执行到疑似漏洞…

【C++LeetCode】【热题100】三数之和【中等】-不同效率的题解【6】

题目&#xff1a; 暴力方法&#xff1a; class Solution { public:vector<vector<int>> threeSum(vector<int>& nums) {vector<vector<int>> res;std::unordered_set<std::string> uniqueValues;//保证结果唯一for(int i0;i<n…

【第2章】MyBatis-Plus代码生成器

文章目录 前言一、安装二、生成方式1.DefaultQuery (元数据查询)2.存在问题 三、快速生成1. 生成代码2. 目录结构 四、交互式总结 前言 全新的 MyBatis-Plus 代码生成器&#xff0c;通过 builder 模式可以快速生成你想要的代码&#xff0c;快速且优雅&#xff0c;跟随下面的代…

vue draggable

一、安装&#xff1a; npm i -S vuedraggablenext 二、代码 <draggable :list"projectOptions" item-key"name" class"w-25" ghost-class"ghost"chosen-class"chosen" update"updateSort" animation"3…

跨境独立站推广策略:有哪些方法与工具?

在出海独立站商家中&#xff0c;推广是必不可少的环节。在你完成网站的搭建&#xff0c;产品的上架&#xff0c;以及网站的运营和优化后&#xff0c;你就可以开始着手推广你的网站了。你的网站是承载你的品牌和产品的主要平台&#xff0c;因此&#xff0c;你需要根据你的品牌和…

Java实现RS485串口通信

博客链接地址 近期&#xff0c;我接到了一个任务&#xff0c;将报警器接入到Java项目中&#xff0c;而接入的方式就是通过RS485接入&#xff0c;本人之前可以说是对此毫无所知。不过要感谢现在的互联网&#xff0c;通过网络我查到了我想要知道的一切&#xff0c;这里记录下本次…

【新闻】金融专业“免进”!私募巨头招聘涌现“新剧情”

A股市场在2024年逐渐出现新的运行特征&#xff0c;这不禁让部分主动投资的私募巨头公司重新登上招聘舞台。 但这一次&#xff0c;他们的招聘方向出现了新的变动。 有些机构有意识的为公司投研团队招聘“衔接”岗&#xff0c;有些则把重点放在了投研动作的交易层。 但这都不如…