【详解】KMP算法——每步配图让你打穿KMP

news2025/1/17 21:55:14

介绍

什么是KMP算法:

KMP算法主要运用串的模式匹配中(简单来说就是在s串中找到一个与t串相等的子串,称为模式匹配)例如s为abcdef,t为bcd,那么就是在s中找到bcd,并返回其在s中的首下标,该算法和BF算法相比有比较大的改进,主要是消除了主串指针的回溯,从而使算法的效率有了某种程度的提高。(了解这些就够了,其他都是废话)

有些读者可能不了解什么使BF算法,下面我简单讲解一下(了解的读者可跳过)

BF算法

可以看到BF算法其实就是类似遍历(一定可行,但是时间复杂度是O(m*n)m为s的字符个数,n为t的字符个数)效率太低了,KMP算法就是对它的改进

思维图如下:

代码如下(解释都在代码里了)

#define  _CRT_SECURE_NO_WARNINGS 1
//BF算法
#include <stdio.h>
#include <string.h>
typedef struct
{
	char data[1000];
	int length;
}SqString;
int index(SqString s, SqString t)
{
	int i = 0, j = 0;
	while (i < s.length && j < t.length)
	{
		if (s.data[i] == t.data[j])  	//继续匹配下一个字符
		{
			i++;				//主串和子串依次匹配下一个字符
			j++;
		}
		else          			//主串、子串指针回溯重新开始下一次匹配
		{
			i = i - j + 1;			//主串从下一个位置开始匹配
			j = 0; 				//子串从头开始匹配
		}
	}
	if (j >= t.length)
		return(i - t.length);  		//返回匹配的第一个字符的下标
	else
		return(-1);        		//模式匹配不成功
}
int main()
{
	SqString s, t;
	strcpy(s.data, "ababcabcacbab");
	strcpy(t.data, "abcac");
	s.length = 13;
	t.length = 5;
	
	printf("位置:%d\n", index(s, t));
	return 1;
}

接着就是我们的重头戏KMP算法,了解了BF算法,我们发现每次比较完,若不匹配,指向s的指针要进行回溯,那有没有办法让指向s的指针不回溯,一直向前这样是不是效率就会高很多。

KMP算法

分为两步

第一步:求next数组(是关于t串的数组)

我先给出代码(如果理解这个代码直接看第二步)

一开始看不懂很正常(因为非常抽象,花掉一个早上理解这个代码都算正常的)

求解next数组代码如下(别小看这个和递归一样小小代码,大大能量)

void GetNext(SqString t, int next[])
{
	int j, k;
	j = 0, k = -1;
	next[0] = -1;
	while (j < t.length - 1)
	{
		if (k == -1 || t.data[j] == t.data[k])
		{
			k++; j++;
			next[j] = k;
		}
		else k = next[k];
	}
}

为什么要求next数组呢,这是因为KMP算法比较过程其实是让t串进行移动(理解这个非常重要),next数组存储当前可以移动多少个字符。

概念:对于t[j]的每个字符存在一个整数k,使得模式串t中开头的k个字符(t[0],t[1],...,t[k-1])依次与t[j]的前面k个字符(t[j-k],t[j-k+1]....t[j-1],这里t[j-k]最多从t[1]开始)。如果这样的k有多个取最大的一个,模式串t中每个位置j的字符都有这种信息,采用next[j]数组表示,及next[j] = MAX(k).

概念如上,不理解很正常下面我会用图画来帮助大家理解(包括我从不懂到理解的一些经验)

 首先总体next我先给出下面会逐一分析

总体next(不懂没关系看下面的)

规定next[0]为-1,这是规定不必深究,B前面只有A,故next[1]为0

找k的规则,大一点的看的比较清楚,我就先讲为什么下标7的B下面的k为什么为3,找的时候一个必须从0开始,另一个的结尾必须是下标7的前一个,7下标对应的k是反应前6个,不包括本身。

其中特别注意(从0开始的哪个不能超过j,从j前面为结尾的不能往前到0),这其实就是前后缀的概念(防止太乱我给了两张图片)

第一步:

next【0】规定为-1,next【1】前面只有A显然为0自己本身不算相等,next【2】要记得我上面讲的前后缀

第二步:


后面类似,就麻烦读者自行完成。

将上面的图解归纳起来就得到了一个求解next数组的公式

下面那个代码再理解理解(上是手算,下面是代码实现要理解一下,特别注意k每次最多增加1,k=next【k】是k回退,因为不相等,读者可以自己画图帮助理解一下)

void GetNext(SqString t, int next[])
{
	int j, k;
	j = 0, k = -1;
	next[0] = -1;
	while (j < t.length - 1)
	{
		if (k == -1 || t.data[j] == t.data[k])
		{
			k++; j++;
			next[j] = k;
		}
		else k = next[k];
	}
}

接着就是

第二步KMP算法的模式匹配过程

一样我先给代码

int KMPIndex(SqString s, SqString t)
{
	int i = 0, j = 0;
	int next[20];
	GetNext(t, next);
	while (i < s.length && j < t.length)
	{
		if (j == -1 || s.data[i] == t.data[j])
		{
			j++; i++;
		}
		else j = next[j];
	}
	if (j >= t.length) return (i-t.length);
	else return -1;
}

有了next数组的帮助,我们就可以知道当模式串t与s串不同时,t串要向右移动多少个字符 

整个过程图解如下:


相信细心的同学发现KMP算法其实是移动t串,s串是一直向前的。

总代码如下

c语言

#define  _CRT_SECURE_NO_WARNINGS 1
//串的模式匹配
//kmp求解
#include <stdio.h>
#include <string.h>
typedef struct
{
	char data[1000];
	int length;
}SqString;
void GetNext(SqString t, int next[])
{
	int j, k;
	j = 0, k = -1;
	next[0] = -1;
	while (j < t.length - 1)
	{
		if (k == -1 || t.data[j] == t.data[k])
		{
			k++; j++;
			next[j] = k;
		}
		else k = next[k];
	}
}
int KMPIndex(SqString s, SqString t)
{
	int i = 0, j = 0;
	int next[20];
	GetNext(t, next);
	while (i < s.length && j < t.length)
	{
		if (j == -1 || s.data[i] == t.data[j])
		{
			j++; i++;
		}
		else j = next[j];
	}
	if (j >= t.length) return (i - t.length);
	else return -1;
}
int main()
{
	SqString s1, s2;
	strcpy(s1.data, "abcdefg");
	strcpy(s2.data, "de");
	s1.length = 7;
	s2.length = 2;
	int t = KMPIndex(s1, s2);
	printf("%d", t);
	return 0;
}

c++版的如下

//KMP算法
#include "sqstring.cpp"
void GetNext(SqString t,int next[])		//由模式串t求出next值
{
	int j,k;
	j=0;k=-1;next[0]=-1;
	while (j<t.length-1) 
	{	
		if (k==-1 || t.data[j]==t.data[k]) 	//k为-1或比较的字符相等时
		{
			printf("(1)j=%d,k=%d,两个指向字符相同",j,k);
			printf(",执行j++,k++ -> ");
			j++;k++;
			next[j]=k;
			printf("(1) j=%d,k=%d,next[%d]=%d\n",j,k,j,k);
       	}
       	else
		{
			printf("(2)j=%d,k=next[%d]=",j,k);
			k=next[k];
			printf("%d\n",k);
		}
	}
}
int KMPIndex(SqString s,SqString t)  //KMP算法
{
	int next[MaxSize],i=0,j=0;
	GetNext(t,next);
	while (i<s.length && j<t.length) 
	{
		if (j==-1 || s.data[i]==t.data[j]) 
		{
			i++;j++;  			//i,j各增1
		}
		else j=next[j]; 		//i不变,j后退
    }
    if (j>=t.length)
		return(i-t.length);  	//返回匹配模式串的首字符下标
    else  
		return(-1);        		//返回不匹配标志
}
/*
int main()
{
	SqString s,t;
	StrAssign(s,"ababcabcacbab");
	StrAssign(t,"abcac");
	printf("s:");DispStr(s);
	printf("t:");DispStr(t);
	printf("位置:%d\n",KMPIndex(s,t));
	return 1;
}
*/

int main()
{
	SqString s,t;
	StrAssign(t,"aaba");
	//StrAssign(t,"aaaac");
	printf("t:");DispStr(t);
	int next[100];
	GetNext(t,next);

	return 1;
}

力扣例题1408

帮助大家巩固知识点。

结语:

其实写博客不仅仅是为了教大家,同时这也有利于我巩固自己的知识点,和一个学习的总结,对文章有任何问题的还请指出,接受大家的批评,让我改进,如果大家有所收获的话还请不要吝啬你们的点赞和收藏,这可以激励我写出更加优秀的文章。

如果大家还是不太理解的话可以评论区提问,我都会解答,或者可以看b站的【最浅显易懂的 KMP 算法讲解-哔哩哔哩】 https://b23.tv/cvI5JKf

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

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

相关文章

【C++】STL 容器 - set 集合容器 ⑦ ( 查找元素 - set#find 函数 | 获取元素个数 - set#count 函数 )

文章目录 一、查找元素 - set#find 函数1、函数原型 简介2、代码示例 - set#find 函数 二、获取元素个数 - set#count 函数1、函数原型 简介2、代码示例 - set#find 函数 一、查找元素 - set#find 函数 1、函数原型 简介 在 C 语言的 STL 标准模板库 , std::set 集合容器 是一个…

优化模型:matlab二次规划

1.二次规划 1.1 二次规划的定义 若某非线性规划的目标函数为自变量 x x x的二次函数&#xff0c;且约束条件全是线性的&#xff0c;则称这种规划模型为二次规划。 1.2 二次规划的数学模型 min ⁡ 1 2 x T H x f T x \min \frac{1}{2}\boldsymbol{x}^{\boldsymbol{T}}\bolds…

Android实验:contentprovider 实验+SQLite 数据库的实现

目录 SQLite实验目的实验内容实验要求项目结构代码实现结果展示 SQLite SQLite 是一个开源的嵌入式关系数据库&#xff0c;实现了自给自足的、无服务器的、配置无需的、事务性的 SQL 数据库引擎。它是一个零配置的数据库&#xff0c;这意味着与其他数据库系统不同&#xff0c;…

三子棋(c语言)

前言&#xff1a; 三子棋是一种民间传统游戏&#xff0c;又叫九宫棋、圈圈叉叉棋、一条龙、井字棋等。游戏规则是双方对战&#xff0c;双方依次在9宫格棋盘上摆放棋子&#xff0c;率先将自己的三个棋子走成一条线就视为胜利。但因棋盘太小&#xff0c;三子棋在很多时候会出现和…

“产品经理必懂的关键术语“

产品经理是现代企业中非常重要的一个角色&#xff0c;他们负责制定产品策略、规划产品开发流程、管理产品质量和用户反馈等等。然而&#xff0c;对于产品经理来说&#xff0c;了解并掌握相关的专业术语是非常重要的。本篇文章会介绍一些产品经理需要掌握的专业术语&#xff0c;…

PIC项目(9)——基于PIC16F877A的环境光照检测系统

1.课题背景 近年来&#xff0c;城市光污染问题逐渐显现。白天&#xff0c;玻璃幕墙、釉面砖墙、磨光大理石和各种涂料等装饰反射光线&#xff0c;明晃刺眼&#xff1b;夜晚&#xff0c;商场、酒店、超市楼顶的广告牌、电子屏、霓虹灯炫烂夺目。面对这样的光污染&#xff0c;人们…

SpringMVC学习与开发(四)

注&#xff1a;此为笔者学习狂神说SpringMVC的笔记&#xff0c;其中包含个人的笔记和理解&#xff0c;仅做学习笔记之用&#xff0c;更多详细资讯请出门左拐B站&#xff1a;狂神说!!! 11、Ajax初体验 1、伪造Ajax 结果&#xff1a;并未有xhr异步请求 <!DOCTYPE html> &…

四.消息队列

目录 1 .消息队列概述 2.消息队列的特点 3.ftok函数 3 创建消息队列-msgget( ) 3.1发送消息-msgsnd( ) 3.2 接收消息-msgrcv( ) 4 消息队列的控制 1 .消息队列概述 消息队列是一种进程间通信的机制&#xff0c;允许不同进程在系统中传递数据。它们通常由内核维护&#x…

c语言-指针练习题

目录 前言一、题目一二、题目二总结 前言 为了巩固c语言中关于指针知识点的掌握&#xff0c;本篇文章记录关于指针的练习题。 一、题目一 有n个整数&#xff0c;使前面各数顺序往后移动m个位置&#xff0c;最后m个数变成最前面的m个数 写一函数实现以上功能&#xff0c;在主函…

【Vue2+3入门到实战】(5)Vue基础之Computed计算属性 详细示例

目录 一、今日学习目标1.computed计算属性 二、computed计算属性1.概念2.语法3.注意4.案例5.代码准备 三、computed计算属性 VS methods方法1.computed计算属性2.methods计算属性3.计算属性的优势4.总结 四、计算属性的完整写法五、综合案例-成绩案例六、Computed计算属性总结 …

荣耀之城(富饶之地)

规则简介 这是一个回合制的游戏&#xff0c;每个回合都是先选角色然后按照角色编号依次执行回合。 8个角色&#xff1a;刺客、小偷、魔术师、国王、住持、商人、建筑师、领主 根据人数的不同&#xff0c;按照不同的规则依次选取一个角色&#xff0c;国王第一个选&#xff0c…

【数学建模美赛M奖速成系列】Matplotlib绘图技巧(二)

Matplotlib绘图技巧&#xff08;二&#xff09; 写在前面2. 函数间区域填充函数fill_between()和fill()参数&#xff1a; 3. 散点图 scatter4. 直方图 hist5. 条形图 bar5.1 一个数据样本的条形图参数&#xff1a; 5.2 多个数据样本进行对比的直方图5.3 水平条形图参数 5.4 绘制…

Redis内存使用率高,内存不足问题排查和解决

问题现象 表面现象是系统登录突然失效&#xff0c;排查原因发现&#xff0c;使用redis查询用户信息异常&#xff0c;从而定位到redis问题 if (PassWord.equals(dbPassWord)) {map.put("rtn", 1);map.put("value", validUser);session.setAttribute("…

结构体:子网掩码

#include<iostream> using namespace std; union IP //创建共用体 {unsigned char a[4];unsigned int ip; }; IP getIP() //获取ip函数 {int a, b, c, d;scanf_s("%d.%d.%d.%d", &a, &b, &c, &d);IP address;address.a[3] a; address.a[2] …

C. Load Balancing 一个序列同时加一个数和减一个数,直到最大和最小之间相差最大为1(结论可记住)

题目&#xff1a; https://atcoder.jp/contests/abc313/tasks/abc313_c 思想&#xff1a;1.给定一个固定的B&#xff0c;求使A等于B所需的最小运算次数 2.在所有最大值和最小值最多相差1的B中&#xff0c;找出一个所需的运算次数最少的&#xff0c;即1 做法&#xff1a;构造…

Vue项目优化-组件配置化、插件使用

Vue中可以根据需要去加载插件&#xff0c;一些自己写的插件在多个项目中都是需要用到的&#xff0c;通过把它们插件化&#xff0c;可以实现在需要用到的项目中便捷地复用&#xff0c;实现热拔插。 一、问题背景 以弹窗表单组件为例&#xff0c;平常我们使用弹窗组件都是通过页面…

C单片机数据类型与格式化

C语言数据类型 关键字位数表示范围stdint关键字ST关键字举例unsigned char80 ~ 255uint8_tu8u8 data 128char8-128 ~ 127int8_ts8s8 temperature 25unsigned short160 ~ 65535uint16_tu16u16 counter 5000short16-32768 ~ 32767int16_ts16s16 position 32767unsigned int3…

[Angular] 笔记 21:@ViewChild

chatgpt: 在 Angular 中&#xff0c;ViewChild 是一个装饰器&#xff0c;用于在组件类中获取对模板中子元素、指令或组件的引用。它允许你在组件类中访问模板中的特定元素&#xff0c;以便可以直接操作或与其交互。 例如&#xff0c;如果你在模板中有一个子组件或一个具有本地…

Autosar MCAL-RH850P1HC Dio配置

文章目录 DioDioGeneralDioCriticalSectionProtectionDioDevErrorDetectDioDeviceNameDioFlipChannelApiDioMaskedWritePortApiDioUseWriteVerifyErrorInterfaceDioVersionCheckExternalModulesDioVersionInfoApiDioWriteVerifyDioWriteVerifyErrorInterface DioPortP0-P9DioPo…

关于mysql8.0相关的升级

不知不觉&#xff0c;MySQL8.0已经有好多个GA小版本了。目前互联网上也有很多关于MySQL8.0的内容了&#xff0c;MySQL8.0版本基本已到稳定期&#xff0c;相信很多小伙伴已经在接触8.0了。本篇文章主要介绍从5.7升级到8.0版本的过程及注意事项&#xff0c;有想做版本升级的小伙伴…