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

news2024/12/24 2:19:23

0.前言

字符串函数strstr相信大家都不陌生–就是在一个字符串(主串)中找查找另一个字符串(子串),并返回子串在主串中的位置。那么这个函数是怎么实现的呢?这就涉及字符串匹配的问题,本章就让我们一起学习有关串匹配的两个算法–BF算法和KMP算法🐇🐇🐇

1.BF算法

BF算法,即暴力(Brute Force)算法,是普通的模式匹配算法,BF算法的思想就是将目标串S的第一个字符与模式串T的第一个字符进行匹配,若相等,则继续比较S的第二个字符和 T的第二个字符;若不相等,则比较S的第二个字符和T的第一个字符,依次比较下去,直到得出最后的匹配结果。BF算法是一种蛮力算法。这是百度百科对BF算法的简介。

图形理解:
在这里插入图片描述

我们假设主串用i下标进行访问,子串用j下标进行访问,刚开始主串i和子串下标j都是从0开始进行两个字符串的匹配。如果匹配到不相等,i下标回到刚刚位置的下一个位置,j下标回到0的位置,直到主串i下标到末尾为止(匹配失败),或子串下标j到达末尾(匹配成功)。

代码实现

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<string.h>
#include<assert.h>
//串的匹配算法--BF算法
int BF(const char* str, const char* sub)
{
	if (str == NULL || sub == NULL)
	{
		return -1;
	}
	int lenstr = strlen(str);
	int lensub = strlen(sub);
	if (lenstr == 0 || lensub == 0)
	{
		return -1;
	}
	int i = 0;//记录主串的位置
	int j = 0;//记录子串的位置
	while (i < lenstr && j < lensub)
	{
		if (str[i] == sub[j])
		{
			i++;
			j++;
		}
		else
		{
			//i和j是同时走的
			i = i - j + 1;//i回到刚才比较的位置的下一个位置
			j = 0;//子串回到起始位置
		}
	}
	while (j >= lensub)
	{
		//返回子串在主串的起始位置
		return i - j;
	}
	return -1;
}
int main()
{
	char arr1[] = "ababcabc";
	char arr2[] = "abcabc";
	printf("%d", BF(arr1, arr2));
	return 0;
}

代码运行的结果如下:
在这里插入图片描述

小结: 假设主串长度为M,子串长度为N,所以BF算法的时间复杂度:O(M*N),用BF算法进行字符串的匹配非常“暴力”。😾😾😾

2.KMP算法

KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt提出的,因此人们称它为克努特—莫里斯—普拉特操作(简称KMP算法)。KMP算法的核心是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现就是通过一个next()函数实现,函数本身包含了模式串的局部匹配信息。这是百度百科对KMP算法的简介。

最长相等前后缀

在认识KMP算法之前,首先我们需要认识什么是相等前后缀,🦀🦀🦀前缀:从字符串第一个字符开始,不包括最后一个字符的字符串部分;后缀:包含最后一个字符,不包括第一个字符的字符串部分
在这里插入图片描述
然后我们得知道主串和子串在匹配失败的时候,主串和子串不相等字符的前面的字符串是一样的,这时只需要找到该字符串的最长相等前后缀长度,主串i不用回退,子串下标j回到的位置(即前缀字符的下一个位置)(即刚好为最长相等前后缀的长度)。
在这里插入图片描述
通过观察可以发现,只需要从在主串中相同部分字符串后缀的下一个位置,在子串中相同部分字符串前缀的下一个位置开始匹配即可。

next数组

要达到上面主串和子串匹配主串下标i不用回退的,子串下标j回到特定位置的效果,需要在主串和子串匹配不相等时,让最长相等前后缀的长度作为子串下标j回退的位置,这时我们可以用数组进行记录,并把这个数组取名为next(意义是j的下一个位置)。那么我们该怎么实现next数组呢?🐰🐨🐻

KMP算法的精髓就是next[j]=k,不同的j值要用一个K来实现,其中K就是子串下标j回退的位置。

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

匹配相等的情况

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

因为已经知道next数组前面两位,我们知道求第i位则就需要判断sub[i - 1] == sub[k],如果相等,next[i]=k+1

匹配不相等的情况

在这里插入图片描述

如果遇到不相等的情况,k回退到next[k]的位置继续,再比较继续比较是否相等,直到匹配到sub[i - 1] == sub[k]相等或者k=-1为止

next数组代码实现:

void Getnext(const char* sub, int* next, int lensub)
{
	//默认next前两个数分别为-1,0
	next[0] = -1;
	next[1] = 0;
	int i = 2;//当前下标的位置
	int k = 0;//前一项的存放的值(回溯的位置)
	while (i < lensub)
	{
		//子串前缀末尾和后缀末尾字符相等
		if (k==-1||sub[i - 1] == sub[k])
		{
			next[i] = k + 1;
			k++;
			i++;
		}
		else
		{
			k = next[k];
		}
	}
}

KMP代码实现

#include<stdio.h>
#include<string.h>
#include<assert.h>
void Getnext(const char* sub, int* next, int lensub)
{
	//默认next前两个数分别为-1,0
	next[0] = -1;
	next[1] = 0;
	int i = 2;//当前下标的位置
	int k = 0;//前一项的存放的值(回溯的位置)
	while (i < lensub)
	{
		//子串前缀末尾和后缀末尾字符相等
		if (k==-1||sub[i - 1] == sub[k])
		{
			next[i] = k + 1;
			k++;
			i++;
		}
		else
		{
			k = next[k];
		}
	}
}
int KMP(const char* str, const char* sub)
{
	if (str == NULL || sub == NULL)
	{
		return -1;
	}
	int lenstr = strlen(str);
	int lensub = strlen(sub);
	if (lenstr == 0 || lensub == 0)
	{
		return -1;
	}
	int* next = (int*)malloc(sizeof(int) * lensub);
	Getnext(sub, next, lensub);
	int i = 0;//记录主串的位置
	int j = 0;//记录子串的位置
	//假设主串和子串在第i个位置不相等
	//那么主串的前i-1个和子串的前i-1个
	//在子串最长相等的前缀和后缀
	//这时只需要在主串的后缀、子串的前缀开始匹配
	//所以主串位置的i不用回退
	//若是前i-1的子串最长相等的前缀和后缀的长度为0
	//那么子串j回到0位置,同时说明从主串前i-1个位置开始匹配
	//都不会和子串匹配,直接从i位置开始匹配
	while (i < lenstr && j < lensub)
	{
		if (j==-1||str[i] == sub[j])
		{
			i++;
			j++;
		}
		else
		{
			j = next[j];
		}
	}
	if (j >= lensub)
	{
		return i - j;
	}
	return -1;
}
int main()
{
	char arr1[] = "aabaabaafa";
	char arr2[] = "aabaaf";
	printf("%d", KMP(arr1, arr2));
	return 0;
}

小结: 假设主串长度为M,子串长度为N,KMP算法的时间复杂度为O(M+N),大大提高字符串匹配的效率,是一个很“友好”的算法。🎉🎉🎉

总结

本章我们一起学习字符串匹配算法–BF算法和KMP算法的实现,希望对大家解决字符串匹配问题有些许帮助!感谢大家阅读,如有不对,欢迎纠正!!!🎠🎠🎠

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

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

相关文章

Node.js 事件循环和事件派发器

目录 1、process.nextTick() 介绍 2、setTimeout() 3、零延迟 4、setInterval() 5、递归setTimeout 6、setImmediate() 7、Node.js 事件派发器 1、process.nextTick() 介绍 Node.js中 process.nextTick函数以一种特殊的方式与事件循环交互。 当你试图理解Node.js事件循…

Redis数据结构——QuickList、SkipList、RedisObjective

承接上文&#xff0c;本文主要介绍QuickList、SkipList、RedisObjective 四、 Redis数据结构-QuickList 问题1&#xff1a;ZipList虽然节省内存&#xff0c;但申请内存必须是连续空间&#xff0c;如果内存占用较多&#xff0c;申请内存效率很低。怎么办&#xff1f; ​ 答&a…

计算机操作系统(慕课版)第二章课后题答案

一、简答题 (1)什么是前趋图&#xff1f;试画出下面四条语句的前趋图. S1&#xff1a;axy&#xff1b; S2&#xff1a;bz1&#xff1b; S3&#xff1a;ca-b&#xff1b; S4&#xff1a;wc1&#xff1b; 答&#xff1a;前趋图(Precedence Graph)是一个有向无循环图&#xff0c;…

chatgpt赋能Python-pythondataframe取出一列

用 Python Dataframe 取出一列 数据分析中&#xff0c;用到的数据往往是有多列多行的。而在实际的分析过程中&#xff0c;我们需要针对其中的某一列进行处理。这个时候&#xff0c;Python中的Dataframe就成了我们的利器。 在这篇文章中&#xff0c;我们将教你如何使用Python …

chatgpt赋能Python-pythongit

PythonGit&#xff1a;使Git操作更加高效 Git作为目前最流行的版本控制工具之一&#xff0c;已经被广泛应用于软件开发、Web开发等领域。PythonGit则是一个基于Python编写的Git客户端库&#xff0c;可以让开发者们更加高效地进行Git操作&#xff0c;提高开发效率。 PythonGit…

Qt Quick系列(2)—核心元素类型(1)

作者&#xff1a;CCAccept 专栏&#xff1a;Qt Quick 文章目录 前言ItemRectangleTextImageMouseArea 总结 前言 Qt Quick的元素分为 1、视觉元素&#xff08;如Rectangle&#xff09;具有几何属性 2、非视觉元素&#xff08;如Timer&#xff09;提供一般功能&#xff0c;用…

learn C++ NO.5 ——类和对象(3)

日期类的实现 在前面类和对象的学习中&#xff0c;由于知识多比较多和碎&#xff0c;需要一个能够将之前所学知识融会贯通的东西。下面就通过实现日期类来对类和对象已经所学的知识进行巩固。 日期类的基本功能&#xff08;.h文件&#xff09; //Date.h//头文件内容 #includ…

makefile 学习(4): makefile基础

0. 官方文档 GNU Make 官方网站: https://www.gnu.org/software/makeGNU Make 官方文档下载地址: https://www.gnu.org/software/make/manual/Makefile Tutorial:https://makefiletutorial.com/ 1.基本要求 1.1 基本格式 targets : prerequisties [tab键] command target : …

一、MongoDB简介

文章目录 一、MongoDB简介1、NoSQL简介2、什么是MongoDB ?3、MongoDB 特点4、安装mongodb5、MongoDB 概念解析5.1 数据库5.2 文档5.3 集合5.4 MongoDB 数据类型 6、适用场景 一、MongoDB简介 1、NoSQL简介 NoSQL(NoSQL Not Only SQL)&#xff0c;意即反SQL运动&#xff0c;…

关于在spyder,jupyter notebook下创建虚拟环境(pytorch,tensorflow)均有效

anaconda下载地址 https://www.anaconda.com/download/ 下载完成后打开anaconda目录下的 anaconda prompt 在命令行中输入下面的命令创建一个叫tf2.0的虚拟环境&#xff08;“tf2.0”是建立的Conda虚拟环境的名字&#xff0c;可以自拟&#xff09; conda create -n tf2.0 p…

chatgpt赋能Python-pythonfor遍历

Python for 遍历&#xff1a;优雅地遍历数据结构 对于任何编程语言来说&#xff0c;遍历是一项基本操作。而在 Python 中&#xff0c;遍历是一项非常简单和优雅的操作。Python 提供了多种遍历数据结构的方法&#xff0c;包括 for 循环、while 循环、迭代器和生成器等。本文将介…

模板和STL【C++初阶】

目录 一、前言 二、函数模板 三、类模板 四、STL 一、前言 以前我们写swap函数时&#xff0c;对每一种类型的变量都要写一份swap函数&#xff0c;但是他们的格式都是一样的&#xff0c;未免有些麻烦 因此&#xff0c;我们今天学习的模板就可以针对广泛的类型而不是具体的类…

chatgpt赋能Python-pythondir

Python dir命令&#xff1a;探索Python模块的秘密 如果你是一名Python开发者&#xff0c;那么你一定或多或少接触过dir这个命令。但是&#xff0c;你了解dir到底能做什么吗&#xff1f;这篇文章将会介绍dir命令的用途、用法以及一些有趣的技巧。 什么是dir命令 简单来说&…

chatgpt赋能Python-pythonfind

Python文件搜索工具Pythonfind 在开发过程中&#xff0c;文件搜索工具是一个非常重要的工具。在大型项目中&#xff0c;可能需要查找特定类型的文件或者在代码库中查找特定的代码块。 Pythonfind是一个非常强大和灵活的python文件搜索工具&#xff0c;可以帮助我们简化这个过程…

chatgpt赋能Python-pythonforelse

Python For Else 详解&#xff1a;用 Python 的人都应该了解的语法结构 在 Python 中&#xff0c;一个常见的语法结构是 for...else。这种语法结构让循环变得更加直接明了&#xff0c;也让代码更加易读和易懂。 什么是 Python For Else 在 Python 中&#xff0c;for...else …

第13章_约束

第13章_约束 1. 约束(constraint)概述 1.1 为什么需要约束 数据完整性&#xff08;Data Integrity&#xff09;是指数据的精确性&#xff08;Accuracy&#xff09;和可靠性&#xff08;Reliability&#xff09;。它是防止数据库中存在不符合语义规定的数据和防止因错误信息的…

chatgpt赋能Python-pythondataframe转置

Python Dataframe 转置 - 了解 Pandas 中 DataFrame 转置的用法 数据分析是现代业务中的一个关键课题。当您在数据分析中使用 Pandas 时&#xff0c;往往会遇到需要转置 DataFrame 的情况。本文中&#xff0c;我们将介绍如何使用 Python 的 Pandas 库中的 DataFrame 转置来实现…

chatgpt赋能Python-pythondoc

PythonDoc&#xff1a;了解Python的文档工具 什么是PythonDoc&#xff1f; PythonDoc是Python官方文档。它是Python编程语言的权威指南和参考资料&#xff0c;提供丰富而全面的信息&#xff0c;从基础语法到高级主题&#xff0c;都有许多实用和详细的文档说明。 PythonDoc的…

chatgpt赋能Python-pythonddos

PythonDDoS&#xff1a;了解一下这种利用Python语言的攻击方式 PythonDDoS&#xff08;Python分布式拒绝服务攻击&#xff09;是一种利用Python语言编写的DDoS攻击技术&#xff0c;它利用了Python的强大处理能力&#xff0c;可以构建高效的攻击工具&#xff0c;让攻击者能够轻…

SpringMVC学习总结(路由映射、参数传递、转发和重定向...)

目录 1. MVC简介 2. SpringMVC简介 3. 路由映射注解 3.1 RequestMapping 3.2 GetMapping与PostMapping 4. 接收前端传递参数 4.1 接收单/多个参数 4.2 接收对象 4.3 接收JSON对象 4.4 后端参数重命名/映射 4.5 设置参数必传/非必传 4.6 获取URL中的参数 4.7 获取文…