【算法】一篇文章弄清楚KMP算法的实现

news2024/10/7 4:36:06

目录

 

前言:

        一.KMP算法简介:

        二.next数组的介绍及实现

        三.next数组的优化

        四.伪代码和完整代码的实现

总结:        


博客主页:张栩睿的博客主页

欢迎关注:点赞+收藏+留言

系列专栏:c语言学习

        家人们写博客真的很花时间的,你们的点赞和关注对我真的很重要,希望各位路过的朋友们能多多点赞并关注我,我会随时互关的,欢迎你们的私信提问,也期待你们的转发!

        希望大家关注我,你们将会看到更多精彩的内容!!!

前言:

        我们都知道,BF算法,就是用两层循环来实现,外层循环用一个循环变量 i 遍历主字符串str1,每当在主字符串中找到子字符串的首元素就进入第二层循环进行两个字符串的匹配,若匹配失败,指针 i 回溯到匹配的起始位置继续寻找下一个子字符串首字符,同时 j 指针也回到子字符串的首地址,重复上述步骤。

        而KMP 算法是 D.E.Knuth、J,H,Morris 和 V.R.Pratt 三位神人共同提出的,称之为 Knuth-Morria-Pratt 算法,简称 KMP 算法。该算法相对于 Brute-Force(暴力)算法有比较大的改进,主要是消除了主串指针的回溯,从而使算法效率有了某种程度的提高。那么,KMP是如何提高算法效率的呢?

   一.KMP算法简介:

        上面说道 KMP 算法主要是通过消除主串指针的回溯来提高匹配的效率的,那么,它是则呢样来消除回溯的呢?就是因为它提取并运用了加速匹配的信息!
        KMP算法在主串和模式串匹配失败时,会利用匹配过程中已经成功匹配的部分字符的相关信息(字符串最大相等前后缀长度),让维护模式串的指针回退到合适的位置而维护主串的指针不进行回退操作,继续向后匹配。kmp算法中维护主串的指针只递增不回退,从而使查找的时间复杂度降为线性复杂度。

        这里我们就不得不引出一个数组:Next数组。这是一个神奇的数组,也是KMP算法最难弄懂的代码。只要弄懂了 next 的求解方法,也就弄懂了 KMP 算法的十之七八了。

对于BF比较,我们逐个比较,时间复杂度大,而且是没有必要的

 所以我们通过KMP算法省去这些多余的比较。

        同学们可能对于移动位数不太理解,其实这些移动的位数就是在我之前说的next数组里面存着的每个元素。

下面,我们就来讲述一下这个奇妙的next数组。

二.next数组的介绍及实现

        什么是next数组呢?实际上,next就是根据字串sub重复的字符构造出来的数组。next的长度和字串的长度是相同的。

 可能看到这里,同学们还是有点蒙,没关系我在来解释一下:

        我们先让next数组与字串sub对齐。只需要找到next数组对应的该位置前缀(前缀不包括该位置的字符)最长的两个相同字串,这两个相同字串的长度就是这个next的值。

我们再来看一个例子:

        对于下标为4的子串,我们发现,sub子串a==a,长度为一,所以下标为4的next数组填入4,同理,下标为5的子串 ,sub子串ab==ab,长度为2,所以我们填入2.。。。。所以我们只需要找到该位置前缀,以字串第一个字符开头,以该位置前一个位置的字符作为结束的字符串即可。

ok,下面咱们分三种情况来讲 next 的求解过程

特殊情况
当 j 的值为 0 或 1 的时候,它们的 k 值都为 0,即 next[0] = 0、next[1] =0。但是为了后面 k 值计算的方便,我们将 next[0] 的值设置成 -1。

当 t[j] == t[k] 的情况
举个栗子

        观察上图可知,当 t[j] == t[k] 时,必然有"t[0]…t[k-1]" == " t[j-k]…t[j-1]",此时的 k 即是相同子串的长度。因为有"t[0]…t[k-1]" == " t[j-k]…t[j-1]",且 t[j] == t[k],则有"t[0]…t[k]" == " t[j-k]…t[j]",这样也就得出了next[j+1]=k+1。

当t[j] != t[k] 的情况
        关于这种情况,在代码中的描述就是“简单”的一句 k = next[k];为什么呢?如果我们的t[j] != t[k],就已经说明之前的t[0]…t[k]" == " t[j-k]…t[j]发生了断裂,不知道大家有没有发现next数组的规律,都是递增,然后突然变为0或1,并且回溯的时候,除了回溯到的第一个元素,其他元素和回溯之前的元素都是一样的!所以回溯的意义其实是找到首元素!下面我会优化这段代码。

void GetNext(int* next, char* sub)
{
	next[0] = -1;
	if (strlen(sub) == 1)
		return;
	next[1] = 0;
	int k = 0;
	int i = 2;
	while (i < strlen(sub))
	{
		if ((k==-1)||sub[i - 1] == sub[k])
		{
			next[i] = k + 1;
			i++;
			k++;
		}
		else
		{
			k = next[k];
		}
	}
}

三.next数组的优化

        上面我们发现,next数组回溯以后得到的元素,和该元素是一样的,而且本质上next数组的意义就是找到首元素!所以我们可以把刚刚的代码优化并且可以更容易理解!

void GetNext(int* next, char* sub)
{
	next[0] = -1;
	if (strlen(sub) == 1)
		return;
	next[1] = 0;
	int k = 0;
	int i = 2;
	while (i < strlen(sub))
	{
		if (sub[i - 1] == sub[k])
		{
			next[i] = k + 1;
			i++;
			k++;
		}
		else if(sub[0]==sub[i-1])
		{
			sub[i]=1;
			i++;
			k++;
		}
		else if (sub[0] != sub[i - 1])
		{
			sub[i] = 0;
			i++;
			k++;
		}
	}
}

四.伪代码和完整代码的实现

原代码:

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<assert.h>
#include<string.h>
#include<stdlib.h>
//str主串
//sub子串
//pos从主串哪里开始找
void GetNext(int* next, char* sub)
{
	next[0] = -1;
	if (strlen(sub) == 1)
		return;
	next[1] = 0;
	int k = 0;
	int i = 2;
	while (i < strlen(sub))
	{
		if (sub[i - 1] == sub[k])
		{
			next[i] = k + 1;
			i++;
			k++;
		}
		else if(sub[0]==sub[i-1])
		{
			sub[i]=1;
			i++;
			k++;
		}
		else if (sub[0] != sub[i - 1])
		{
			sub[i] = 0;
			i++;
			k++;
		}
	}
}
int KMP(char* str, char* sub, int pos)
{
	assert(str && sub);
	int lenstr = strlen(str);
	int lensub = strlen(sub);
	if (pos < 0 || pos >= lenstr)return -1;
	int* next = (int*)malloc(sizeof(int) * lensub);
	GetNext(next, sub);
	int i = pos;
	//遍历主串
	int j = 0;
	//遍历字串
	while (i < lenstr && j < lensub)
	{
		if ((j == -1) || (str[i] == sub[j]))
		{
			i++;
			j++;
		}
		else
		{
			j = next[j];
		}
	}
	free(next);
	if (j >= lensub)
		return i - j;
	return -1;
}
int main()
{
	printf("%d", KMP("abcabcaad", "ad", 0));
}

总结:       

        KMP算法是鹏哥让我们课余时间研究的一个算法,虽然弄懂她花了我好多时间,但是真正搞清楚以后就非常的开心。辛苦各位小伙伴们动动小手,三连走一波 最后,本文仍有许多不足之处,欢迎各位认真读完文章的小伙伴们随时私信交流、批评指正!

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

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

相关文章

小白的性能测试探索之路(1)

​​​​​​​ 某一天&#xff0c;领导突然就拉了个会说&#xff0c;我们成立稳定性专项&#xff0c;以测试为主力提升服务的整体稳定性&#xff1f; 当时我的内心是&#xff1a;“what”&#xff0c;性能测试我完全没接触过呀&#xff0c;i am a little tester&#xff5e;而…

华为MPLS-HubSub组网实验配置

目录 配置接口IP地址以及底层IGP协议 配置MPLS LDP协议 Sub与Hub建立Vpnv4邻居 配置PE与CE对接命令 Sub-PE与CE对接配置 Hub与CE对接配置 发现问题 PE与CE之间都采用EBGP方式进行路由传递 MPLS隧道——Hub&Spoke组网_静下心来敲木鱼的博客-CSDN博客_hub spokehttps…

Flume第二章:企业案例

系列文章目录 Flume第一章&#xff1a;环境安装 Flume第二章&#xff1a;企业案例 文章目录系列文章目录前言一、复制和多路复用1.案例需求2.案例实现3.结果查看二、负载均衡和故障转移1.需求案例2.案例实现3.结果查看三、聚合1.案例需求2.案例实现3.查看结果总结前言 这次我…

用python写的代码输入助手小程序(附源码)

命令太多&#xff0c;很容易忘记&#xff0c;还有很多代码片段想保存下来用到的时候能够快速输入&#xff0c;提高开发效率。在网上找了很多&#xff0c;发现都不是自己想要的。于是就用python写了一个自己用的代码输入助手小程序&#xff0c;我自己已经用了很长时间了&#xf…

工业中常用流量计及其测量原理

一、流量计单位 工程上常用单位m3/h&#xff0c;它可分为瞬时流量&#xff08;Flow Rate&#xff09;和累计流量&#xff08;Total Flow&#xff09;&#xff0c;瞬时流量即单位时间内过封闭管道或明渠有效截面的量&#xff0c;流过的物质可以是气体、液体、固体&#xff1b;累…

Introduction to Multi-Armed Bandits——02 Stochastic Bandits

Introduction to Multi-Armed Bandits——02 Stochastic Bandits 参考资料 Slivkins A. Introduction to multi-armed bandits[J]. Foundations and Trends in Machine Learning, 2019, 12(1-2): 1-286. 在线学习(MAB)与强化学习(RL)[2]&#xff1a;IID Bandit的一些算法 B…

化繁为简、性能提升 -- 在WPF程序中,使用Freetype库心得

本人使用WPF开发了一款OFD阅读器&#xff0c;显示字体是阅读器中最重要的功能。处理字体显示有多种方案&#xff0c;几易其稿&#xff0c;最终选用Freetype方案。本文对WPF中如何使用Freetype做简单描述。 OFD中有两种字体&#xff1a;嵌入字体和非嵌入字体。1&#xff09; 非…

【vue2】vue生命周期的理解

&#x1f973;博 主&#xff1a;初映CY的前说(前端领域) &#x1f31e;个人信条&#xff1a;想要变成得到&#xff0c;中间还有做到&#xff01; &#x1f918;本文核心&#xff1a;vue生命周期的介绍、vue生命周期钩子函数详解&#xff0c;vue生命周期的执行顺序 目录 …

使用管控平台管理redis集群

1 添加redis集群 在数据库资源中添加redis集群&#xff0c;配置参数并将URL中cluster调整为true。 2 验证配置资源是否正常 3 操作redis数据库中的数据 可以通过使用图形化界面或者命令窗口进行Redis数据库的CRUD 3.1 图形化界面操作 操作Redis字符串列表 3.1.1 新增 右…

Apache Iceberg 背后的设计

原文地址: 阿帕奇冰山&#xff1a;幕后的建筑外观 |德雷米奥 (dremio.com)绝对的精品文章!!!机器翻译和自我调整组成了这篇文章&#xff0c;供大家学习。介绍数据湖的构建希望是实现数据民主化&#xff0c;以允许越来越多的人员、工具和应用程序使用越来越多的数据。实现这一目…

十五天学会Autodesk Inventor,看完这一系列就够了(八),图框自定义

所周知&#xff0c;Autocad是一款用于二维绘图、详细绘制、设计文档和基本三维设计&#xff0c;现已经成为国际上广为流行的绘图工具。Autodesk Inventor软件也是美国AutoDesk公司推出的三维可视化实体模拟软件。因为很多人都熟悉Autocad&#xff0c;所以再学习Inventor&#x…

【数据库数据恢复】华为云mysql数据库数据被delete的数据恢复案例

数据库数据恢复环境&#xff1a; 华为云ECS&#xff0c;linux操作系统&#xff1b; mysql数据库&#xff0c;实例内数据表默认存储引擎为innodb。 数据库故障&#xff1a; 在执行数据库版本更新测试时&#xff0c;用户误将本应在测试库测试的sql脚本执行在生产库中&#xff0c…

拉伯证券|芯片半导体迎来“行业底部出清”,大资金进场迹象明显

近期关于小芯片的利好不断&#xff0c;英特尔近期就发布了根据小芯片技能的处理器&#xff0c;而近期长电科技也在小芯片范畴获得突破。据长电科技在互动平台表明&#xff0c;公司现已完成4nm工艺制程手机芯片的封装&#xff0c;最大封装体面积约为1500平方毫米的体系级封装。长…

人工智能所需高等数学知识大全(收藏版)

来源&#xff1a;投稿 作者&#xff1a;愤怒的可乐 编辑&#xff1a;学姐 不懂数学是学不好人工智能的&#xff0c;本系列文章就汇总了人工智能所需的数学知识。本文是高等数学篇。 另有线代篇和概率论篇 函数与极限 函数 yf(x) ,x是函数f的自变量&#xff0c;y是因变量 函…

数据结构(4)线段树、延迟标记、扫描线

活动 - AcWing 参考-《算法竞赛进阶指南》 一、延迟标记&#xff08;懒标记&#xff09; 在线段树的区间查询命令中&#xff0c;每当遇到被查询区间[l,r]完全覆盖节点时&#xff0c;可以直接把节点上的答案作为备选答案返回。我们已经证明&#xff0c;这样操作的复杂度为O(4…

01-React(脚手架+MVC/MVVM+JSX)

使用 create-react-app 构建React工程化项目 安装 create-react-app $ npm i create-react-app -g 「mac需要加sudo」 基于脚手架创建项目「项目名称需要符合npm包规范」 $ create-react-app xxx 目录结构&#xff1a; |- node_modules 包含安装的模块 |- public 页面模板…

79.循环神经网络的从零开始实现

从头开始基于循环神经网络实现字符级语言模型。 这样的模型将在H.G.Wells的时光机器数据集上训练。 和之前一样&#xff0c; 我们先读取数据集。 %matplotlib inline import math import torch from torch import nn from torch.nn import functional as F from d2l import to…

Rockchip开发系列 - 9.watchdog看门狗

By: fulinux E-mail: fulinux@sina.com Blog: https://blog.csdn.net/fulinus 喜欢的盆友欢迎点赞和订阅! 你的喜欢就是我写作的动力! 目录 dts中的watchdog节点watchdog驱动文件TRM watchdog:WDT框图功能描述计数器中断系统复位复位脉冲长度操作流程图寄存器描述寄存器设置…

Nessus Host Discovery

系列文章 Nessus介绍与安装 Nessus Host Discovery 1.启动nessus cd nessus sh qd_nessus.sh2.进入nessus网站 https://192.168.3.47:8834/3.点击【New Scan】 4.选择【Host Discovery】 5.输入name【主机发现】&#xff0c;Description【主机发现】&#xff0c;Targets【…

Android 蓝牙开发——服务启动流程(二)

首先我们要知道&#xff0c;主要系统服务都是在 SystemServer 启动的&#xff0c;蓝牙也是如此&#xff1a; 1、SystemServer 源码路径&#xff1a;/frameworks/base/services/java/com/android/server/SystemServer.java private void startOtherServices(NonNull TimingsT…