KMP算法,看这篇就够了!
普通的模式匹配算法(BF算法)
子串的定位操作通常称为模式匹配算法
假设有一个需求,需要我们从串“a b a b c a b c a c b a b"中,寻找内容为“a b c a c”的子串。
此时,称“a b a b c a b c a c b a b"为主串S,“a b c a c”为模式串T。
很容易想到,通过遍历主串S,与模式串T的首字母逐一比对,当主串S中的某一元素i与模式串T首字符j相同,则将主串S中第i+1个字符与模式串T的j+1个字符继续匹配。若匹配成功,则继续将主串S中的第i+2个字符与模式串T中的第j+2个字符进行比对。若匹配失败,则将主串S的第i+1个字符与模式串的第j个字符重新比对....
将上述文字转化为图像如下:

按照动画的显示效果很容易理解BF模式匹配算法。
通过分析可以得出,每次匹配失败之后,i的指向又将回到主串S的i-j+2位置
通过C语言伪代码的方式实现:

若将主串S的长度看作m,将模式串T的长度看作n
则该代码的时间复杂度为O(m+n)
BF算法确实实现了模式匹配的目的,但同时也有较大的缺陷
模式匹配改进算法(KMP算法)的引出
假设目前有这样一个主串S与模式串T
主串S:

模式串T:

比对过程:

显而易见,使用BF模式匹配算法,一旦遇到主串S元素与模式串T高度重合,但鲜有不同。
此种算法将进行大量重复的“主串S回退”,如此一来,时间复杂度将达到
O(m*n)
而后,D.E.Knuth与V.R.Pratt和J.H.Morris发现了一套模式匹配的改进算法,根据他们的名字的字母,该算法被命名为:
KMP算法
KMP算法
KMP算法基本概念
KMP算法可以在时间复杂度为O(m+n)的时间数量级上完成模式匹配操作。
其不同点在于,在匹配失败之后,不需要回溯i指针
而是利用已经“部分匹配”的结果,将模式串T向右滑动尽可能远的距离

(KMP算法比对过程1)

(KMP算法比对过程2)

(KMP算法比对过程3)
从该描述中我们提取出要使用KMP算法最核心的三个问题:1.滑动的条件 2.滑动的模式 3.滑动距离k的求解
滑动的条件
这部分我们探究当发生“失配”后,主串S中的i应该与模式串T中的第几个字符(这里用K指代)继续进行比较。
在什么条件下我们可以将窗口进行“滑动”?或者说,怎样才叫发生了“部分匹配”?
这里给出严蔚敏版的《数据结构》中,对于“部分匹配”条件的定义(模式串为p,主串为s):
①
\]
②
\]
③
\]
刚看到这三个公式的时候,有点懵,但仔细比对之后可以发现
公式①说明:模式串p从头开始的子串与后面某段已发生“部分匹配”的主串s的子串q相同
公式②说明:模式串p在除开头以外,有一段子串与刚才的子串q发生了“部分匹配”
公式③说明:如果满足上述两个条件,则可以得出模式串p中有两端相同的子串
如果满足以上三个条件(满足前两个条件则第三个必定满足),则可以快速“滑动k个位置”来进行KMP模式匹配
滑动的模式
明确了前提条件之后,我们建立应该next[j]来保存每次比对结束后的K值
约定:
| next[j] | 条件 | 结果 |
|---|---|---|
| 0 | 当j等于1 | 将i向后移动一位 |
| k | 取Kmax,1<k<j 且 满足条件③ | 将模式串的第k位与当前i对齐 |
| 1 | 其他情况 | 将模式串的第1位与当前i对齐 |
反映成代码形式:

滑动距离K的求解
由
\]
- 可知next[j]仅与模式串有关而与主串无关
这里是整个KMP算法的核心部分,在我反反复复看几十遍严蔚敏版的《数据结构》之后,我终于理清了整个求解滑动距离的方法。
整个算法分为两个情况:
\]
以及
\]
若满足第一种情况,有
next[j+1] = next[j] + 1;
若满足第二种情况,则又分为两种情况
- 设K' = next[k],当P[K'] = P[j] 时,有
next[j+1] = next[k] + 1; - 如果一直移动K'到j = 1时,还不能找到对应的P[K'] = P[j],那么直接有
next[j+1] = 1;
在展开讲求next[j]的算法之前,必须要明确的一点是:
- k,j,k',j+1分别代表串中的哪些位置
在这里给出明确定义:
- j+1就是你需要求k值的模式串位置
- j就是当前需要求出K值的字符的前一个字符
- k-1就是“已匹配”的前缀的最后一个字符
- k就是“已匹配”的前缀的最后一个字符的后一个字符
注意
- 前缀一定要包含第1个字符
- 后缀一定要包含希望求得K的字符的前一个字符

如图所示,如果我们要求得串中第5个字符的K值,假设前四个字符K值均已经求得,设我们的目标字符指针为j+1
又有第1个‘a’与第3个‘a'匹配,所以他们分别为k-1和j-1
因此第2个字符为k,第4个字符为j。
- 重点来了
让我们抛出一个例子,来理解next[j]的求法
假设我们已知j=1,2,3,4的next[j]值分别为:0,1,1,2
由于k-1和j-1已经”匹配“,则满足前置匹配条件,我们来比较pk和pj的内容,
pk = b,pj = a;
可见它们并不相等,因此k指向的b会甩锅给“next[k]”字符,而next[k] = 1,也就是串的第一个字符‘a'
第一个字符‘a'成功与第j个字符‘a'相匹配,因此依照我们定义的的pk ≠ pj的第一种情况可以得到
next[j+1] = next[k] + 1 = 2;
由此,我们可以通过之前定义的方式来确定所有的next[j]的值,下面给出串'abaabcac'的所有next[j]的值:
希望读者能拿起笔,从next[2]开始,计算到next[8]

相信计算完上表格的读者,已经对next[j]的计算方法有了更加深刻的理解
在这里我也分享出自己总结的口诀:
- k,j相等,直接加1
- k,j不等,层层甩锅
- 甩锅失败,直接赋1
- 甩锅成功,老板加1
释义:
- pk = pj:next[j+1] = next[j] + 1;
- pk ≠ pj: 甩锅
- p[k'] = pj:next[j+1] = next[k] + 1;
- p[k'] ≠ pj:next[j+1] = 1 or 继续向next[k']甩锅。
接下来给出代码实现求next[j]:

KMP算法的改进
通过分析代码我们可以发现,当模式串元素有多个重复元素:

他们的next[j]为:0,1,2,3,4
因此在比对时将出现i不动,j从调用next[j]3次的情况,
在这种情况下,我们可以让j=1,2,3,4只进行一次比对就使得i向后移动一位(也就是next[j] = 0)
改进算法如下:

改进之后next[j] = 0,0,0,0,4,成功避免了重复比对
KMP算法,看这篇就够了!的更多相关文章
- Python GUI之tkinter窗口视窗教程大集合(看这篇就够了) JAVA日志的前世今生 .NET MVC采用SignalR更新在线用户数 C#多线程编程系列(五)- 使用任务并行库 C#多线程编程系列(三)- 线程同步 C#多线程编程系列(二)- 线程基础 C#多线程编程系列(一)- 简介
Python GUI之tkinter窗口视窗教程大集合(看这篇就够了) 一.前言 由于本篇文章较长,所以下面给出内容目录方便跳转阅读,当然也可以用博客页面最右侧的文章目录导航栏进行跳转查阅. 一.前言 ...
- React入门看这篇就够了
摘要: 很多值得了解的细节. 原文:React入门看这篇就够了 作者:Random Fundebug经授权转载,版权归原作者所有. React 背景介绍 React 入门实例教程 React 起源于 ...
- [转]React入门看这篇就够了
摘要: 很多值得了解的细节. 原文:React入门看这篇就够了 作者:Random Fundebug经授权转载,版权归原作者所有. React 背景介绍 React 入门实例教程 React 起源于 ...
- ASP.NET Core WebApi使用Swagger生成api说明文档看这篇就够了
引言 在使用asp.net core 进行api开发完成后,书写api说明文档对于程序员来说想必是件很痛苦的事情吧,但文档又必须写,而且文档的格式如果没有具体要求的话,最终完成的文档则完全取决于开发者 ...
- .NET Core实战项目之CMS 第二章 入门篇-快速入门ASP.NET Core看这篇就够了
作者:依乐祝 原文链接:https://www.cnblogs.com/yilezhu/p/9985451.html 本来这篇只是想简单介绍下ASP.NET Core MVC项目的(毕竟要照顾到很多新 ...
- 想了解SAW,BAW,FBAR滤波器的原理?看这篇就够了!
想了解SAW,BAW,FBAR滤波器的原理?看这篇就够了! 很多通信系统发展到某种程度都会有小型化的趋势.一方面小型化可以让系统更加轻便和有效,另一方面,日益发展的IC**技术可以用更低的成本生产 ...
- [译]ASP.NET Core Web API 中使用Oracle数据库和Dapper看这篇就够了
[译]ASP.NET Core Web API 中使用Oracle数据库和Dapper看这篇就够了 本文首发自:博客园 文章地址: https://www.cnblogs.com/yilezhu/p/ ...
- ExpandoObject与DynamicObject的使用 RabbitMQ与.net core(一)安装 RabbitMQ与.net core(二)Producer与Exchange ASP.NET Core 2.1 : 十五.图解路由(2.1 or earler) .NET Core中的一个接口多种实现的依赖注入与动态选择看这篇就够了
ExpandoObject与DynamicObject的使用 using ImpromptuInterface; using System; using System.Dynamic; names ...
- Vue学习看这篇就够
Vue -渐进式JavaScript框架 介绍 vue 中文网 vue github Vue.js 是一套构建用户界面(UI)的渐进式JavaScript框架 库和框架的区别 我们所说的前端框架与库的 ...
- 【转】ASP.NET Core WebApi使用Swagger生成api说明文档看这篇就够了
原文链接:https://www.cnblogs.com/yilezhu/p/9241261.html 引言 在使用asp.net core 进行api开发完成后,书写api说明文档对于程序员来说想必 ...
随机推荐
- 这两个基础seo插件,wordpress网站必装
WordPress对搜索引擎非常友好,这一点很多人都知道.不过我们在制作完成WordPress主题后,还可以在原来的良好基础上,添加两个队seo非常有利的WordPress插件. 第一个插件:Baid ...
- JAVA 150道笔试题知识点整理
JAVA 笔试题 整理了几天才整理的题目,都是在笔试或者面试碰到的,好好理解消化下,对你会有帮助,祝你找工作顺利,收到满意的 offer . 1.Java 基础知识 1.1 Java SE 语法 &a ...
- Kubernetes全栈架构师(资源调度下)--学习笔记
目录 StatefulSet扩容缩容 StatefulSet更新策略 StatefulSet灰度发布 StatefulSet级联删除和非级联删除 守护进程服务DaemonSet DaemonSet的使 ...
- netty 处理客户端连接
Netty如何处理连接事件 上文讲了Netty如何绑定端口,现在我们来阅读下netty如何处理connect事件.上文我们说了NioEventLoop启动后不断去调用select的事件,当客户端连接时 ...
- CentOS7安装Python3和VIM8
参考:http://blog.sina.com.cn/s/blog_45249ad30102yulz.html
- InstallSheild相关
一.关于使用InstallSheild制作安装包的总结. 1.定制化制作需要了解InstallScript语法,相关资料可以去网上查找,后续提供比较好的资料. 2.有些软件运行是需要一些环境的,譬如使 ...
- Java初步学习——2021.10.05每日总结,第五周周三
(1)今天做了什么: (2)明天准备做什么? (3)遇到的问题,如何解决? 今天学了对象与类,如何定义类和创建对象,以及构建方法的用法. 明天课比较多,把今天未学的例子敲一遍好了. 没有遇到什么问题.
- 题解 [HAOI2016]字符合并
题目传送门 Description 有一个长度为 \(n\) 的 \(01\) 串,你可以每次将相邻的 k 个字符合并,得到一个新的字符并获得一定分数. 得到的新字符和分数由这 k 个字符确定.你需要 ...
- Java多线程编程实战指南 核心篇 读书笔记
锁 volatile CAS final static 原子性保障 具备 具备 具备 不涉及 不涉及 可见性保障 具备 具备 不具备 不具备 具备① 有序性保证 具备 具备 不涉及 具备 具备② 上下 ...
- 如何在印刷品中使用遵循SIL Open Font License协议的字体
如何在印刷品中使用遵循SIL Open Font License协议的字体 昨天在知乎看到了一个问题,( 如何在设计中声明字体开源许可证? - 知乎 (zhihu.com),恰好最近在研究一些开源协议 ...