[转]数据结构KMP算法配图详解(超详细)
KMP算法配图详解
前言
KMP解决的问题类型
图解KMP
a | b | c | a | b | c | m | n |
---|---|---|---|---|---|---|---|
next[0] | next[1] | next[2] | next[3] | next[4] | next[5] | next[6] | next[7] |
-1 | 0 | 0 | 0 | 1 | 2 | 3 | 0 |
本例中的蓝色c处出现了不匹配(是s[5]!=t[5]),
我们把子串移动,也就是让s[5]与t[5]前面字符串的最长相等前缀后一个字符再比较,而该字符的位置就是t[?],很明显这里的?是2,就是不匹配的字符前的字符串 最长相等前后缀的长度。
也是不匹配的字符处的next数组next[5]应该保存的值,也是子串回溯后应该对应的字符的下标。 所以?=next[5]=2。接下来就是比对是s[5]和t[next[5]]的字符。这里也是最奇妙的地方,也是为什么KMP算法的代码可以那么简洁优雅的关键。
我们可以总结一下,next数组作用有两个:
一是之前提到的,
next[i]的值表示下标为i的字符前的字符串最长相等前后缀的长度。
二是:
表示该处字符不匹配时应该回溯到的字符的下标
next有这两个作用的源头是:之前提到的字符串的最长相等前后缀
想一想是不是觉得这个算法好厉害,从而不得不由衷佩服KMP算法的创始人们。
KMP算法的时间复杂度
求next数组的代码
typedef struct
{ char data[MaxSize];int length; //串长
} SqString;
//SqString 是串的数据结构
//typedef重命名结构体变量,可以用SqString t定义一个结构体。
void GetNext(SqString t,int next[]) //由模式串t求出next值
{int j,k;j=0;k=-1;next[0]=-1;//第一个字符前无字符串,给值-1while (j<t.length-1) //因为next数组中j最大为t.length-1,而每一步next数组赋值都是在j++之后//所以最后一次经过while循环时j为t.length-2{ if (k==-1 || t.data[j]==t.data[k]) //k为-1或比较的字符相等时{ j++;k++;next[j]=k;//对应字符匹配情况下,s与t指向同步后移//通过字符串"aaaaab"求next数组过程想一下这一步的意义//printf("(1) j=%d,k=%d,next[%d]=%d\n",j,k,j,k);}else{k=next[k];**//我们现在知道next[k]的值代表的是下标为k的字符前面的字符串最长相等前后缀的长度//也表示该处字符不匹配时应该回溯到的字符的下标//这个值给k后又进行while循环判断,此时t.data[k]即指最长相等前缀后一个字符**//为什么要回退此处进行比较,我们往下接着看。其实原理和上面介绍的KMP原理差不多//printf("(2) k=%d\n",k);}}
}
解释next数组构造过程中的回溯问题
大家来看下面的图:
下面的长条代表子串,红色部分代表当前匹配上的最长相等前后缀,蓝色部分代表t.data[j]。
KMP算法代码解释
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); //返回不匹配标志
}
KMP算法的改进
下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|---|
子串 | a | b | a | b | a | a | a | b |
next | -1 | 0 | 0 | 1 | 2 | 3 | 1 | 1 |
nextval | -1 | 0 | -1 | 0 | -1 | 3 | 1 | 0 |
我们来分析下代码:
void GetNextval(SqString t,int nextval[])
//由模式串t求出nextval值
{int j=0,k=-1;nextval[0]=-1;while (j<t.length) {if (k==-1 || t.data[j]==t.data[k]) { j++;k++;if (t.data[j]!=t.data[k])
//这里的t.data[k]是t.data[j]处字符不匹配而会回溯到的字符
//为什么?因为没有这处if判断的话,此处代码是next[j]=k;
//next[j]不就是t.data[j]不匹配时应该回溯到的字符位置嘛nextval[j]=k;else nextval[j]=nextval[k];
//这一个代码含义是不是呼之欲出了?
//此时nextval[j]的值就是就是t.data[j]不匹配时应该回溯到的字符的nextval值
//用较为粗鄙语言表诉:即字符不匹配时回溯两层后对应的字符下标}else k=nextval[k]; }}
int KMPIndex1(SqString s,SqString t)
//修正的KMP算法
//只是next换成了nextval
{int nextval[MaxSize],i=0,j=0;GetNextval(t,nextval);while (i<s.length && j<t.length) {if (j==-1 || s.data[i]==t.data[j]) { i++;j++; }else j=nextval[j];}if (j>=t.length) return(i-t.length);elsereturn(-1);
}
剩下的话
在写这篇博客时,我想起了编译原理老师讲过的一些知识,其实KMP算法的步骤与自动机有不少相似之处,有兴趣的朋友不妨联系对比一下。
参考书籍
[转]数据结构KMP算法配图详解(超详细)相关推荐
- 数据结构KMP算法配图详解(超详细)
KMP算法配图详解 前言 KMP算法是我们数据结构串中最难也是最重要的算法.难是因为KMP算法的代码很优美简洁干练,但里面包含着非常深的思维.真正理解代码的人可以说对KMP算法的了解已经相当深入了.而 ...
- JavaScript数据结构与算法——链表详解(下)
在JavaScript数据结构与算法--链表详解(上)中,我们探讨了一下链表的定义.实现原理以及单链表的实现.接下来我们进一步了解一下链表的其他内容. 1.双向链表 双向链表实现原理图: 与单向链表不 ...
- JavaScript数据结构与算法——链表详解(上)
注:与之前JavaScript数据结构与算法系列博客不同的是,从这篇开始,此系列博客采用es6语法编写,这样在学数据结构的同时还能对ECMAScript6有进一步的认识,如需先了解es6语法请浏览ht ...
- JavaScript数据结构与算法——队列详解(下)
接下来会借助本人另一篇文章JavaScript数据结构与算法--队列详解(上)中实现的队列类及其方法实现一个应用. 配对问题 需求分析:在一个文件中保存着一份男女混合的数据,名称前以B开头表示男士,以 ...
- JavaScript数据结构与算法——列表详解(下),基于Nodejs实现一个列表应用
1.上篇回顾: 上篇我们实现了一个列表类,并添加了一些属性,实现了比较多的方法,本文章将与大家一起使用列表实现一个图书借阅查询系统.需要使用JavaScript数据结构与算法--列表详解(上)中写好的 ...
- JavaScript数据结构与算法——列表详解(上)
列表是一组有序的数据,每个数组中的数据项称为元素.数组相关知识不够了解的伙伴可以阅读本人上篇博客在JavaScript中,列表的元素可以是任意数据类型.列表中可以保存不定数量的元素,实际使用时元素的数 ...
- JavaScript数据结构与算法——数组详解(下)
1.二维与多维数组 JavaScript只支持一维数组,但是通过在数组里保存数组元素的方式,可以轻松创建多维数组. 1.1 创建二维数组 二维数组类似一种由行和列构成的数组表格,在JavaScript ...
- c++数据结构中 顺序队列的队首队尾_数据结构与算法—队列详解
前言 栈和队列是一对好兄弟,前面我们介绍过数据结构与算法-栈详解,那么栈的机制相对简单,后入先出,就像进入一个狭小的山洞,山洞只有一个出口,只能后进先出(在外面的先出去).而队列就好比是一个隧道,后面 ...
- 【数据结构与算法】详解什么是图结构,并用代码手动实现一个图结构
本系列文章[数据结构与算法]所有完整代码已上传 github,想要完整代码的小伙伴可以直接去那获取,可以的话欢迎点个Star哦~下面放上跳转链接 https://github.com/Lpyexplo ...
最新文章
- java大文件读写操作
- 使用Visual Studio Code 开发.NET Core应用程序
- 【Leetcode | 52】257. 二叉树的所有路径
- python调用窗口找到文件,使用Python在Mac OS X中查找当前活动窗口
- 河南科技大学计算机信息安全技术考试,关于申报2020年信息安全等级保护项目的通知...
- springcloud服务调用以及整合Hystrix
- Python编程技巧合集
- unity中使用protobuf-net
- Python语法报错相关语句
- c语言判断一个已知的二叉树是否是二叉排序树_从ServiceComb学习写好go代码---二叉排序树...
- Pycharm如何调整代码颜色以及字体样式
- 【sklearn第二十三讲】异常检测
- 滴滴宣布架构大调整,强调安全第一
- 《EMCAScript6入门》读书笔记——16.Generator函数的语法
- Dagger 2 系列(一) -- 前奏篇:依赖注入的基本介绍
- 微信开发者工具及其文档
- YOLOV5训练数据(火焰检测)
- Mac 播放器 IINA 精确控制失效,调节了快捷键也会关键帧快进。
- php mysql scalar_symfony和doctrine及数据库操作 | 学步园
- Winform 俄罗斯方块儿练习
热门文章
- SQL Server日期函数集合
- 我对CTO的理解 CTO要有技术魅力[转载]
- TTL expired in transit--问题篇~
- 如何使用Tasklist命令
- WPF 实现调用本机摄像头~
- YARP(Yet Another Reverse Proxy)是使用 .NET 构建的高度可定制的反向代理
- 天了噜!定义static字段还有顺序要求?
- 创建工程师文化的3个步骤 | IDCF
- ASP.NET Core官方文档+源码,这样学效率高10倍!
- 用啥Selenium?! .NET程序员就用自家的Playwright for .NET