kmp算法分析和C++实现
知乎高赞分析
作者:逍遥行
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
角色:
甲:abbaabbaaba
乙:abbaaba
乙对甲说:「帮忙找一下我在你的哪个位置。」
甲从头开始与乙一一比较,发现第 7 个字符不匹配。
要是在往常,甲会回退到自己的第 2 个字符,乙则回退到自己的开头,然后两人开始重新比较。这样的事情在字符串王国中每天都在上演:不匹配,回退,不匹配,回退,……
但总有一些妖艳字符串要花出自己不少的时间。
上了年纪的甲想做出一些改变。于是甲把乙叫走了:「你先一边玩去,我自己研究下。」
甲给自己定了个小目标:发生不匹配,自己不回退。
甲发现,若要成功与乙匹配,必须要匹配 7 个字符。也就是说,就算自己回退了,在后续的匹配流程中,肯定还要匹配自己的第 7 个字符。
当在甲的某个字符 c 上发生不匹配时,甲即使回退,最终还是会重新匹配到字符 c 上。
那干脆不回退,岂不美哉!
甲不回退,乙必须回退地尽可能少,并且乙回退位置的前面那段已经和甲匹配,这样甲才能不用回退。
如何找到乙回退的位置?
「不匹配发生时,前面匹配的那一小段 abbaab 于我俩是相同的」,甲想,「这样的话,用 abbaab 的头部去匹配 abbaab 的尾部,最长的那段就是答案。」
具体来说,
abbaab 的头部有 a, ab, abb, abba, abbaa(不包含最后一个字符。下文称之为「前缀」)
abbaab 的尾部有 b, ab, aab, baab, bbaab(不包含第一个字符。下文称之为「后缀」)
这样最长匹配是 ab,乙回退到第三个字符和甲继续匹配。
「要计算的内容只和乙有关」,甲想,「那就假设乙在所有位置上都发生了不匹配,乙在和我匹配之前把所有位置的最长匹配都算出来(算个长度就行),生成一张表,之后我俩发生不匹配时直接查这张表就行。」
据此,甲总结出了一条甲方规则:
所有要与甲匹配的字符串,必须先自身匹配:对每个子字符串 [0...i],算出其「相匹配的前缀与后缀中,最长的字符串的长度」。
甲把乙叫了回来,告诉他新出炉的甲方规则。
「小 case,我对自己还不了解吗」,乙眨了一下眼睛,「那我回退到第三个字符和你继续匹配就行了」。
原理分析
在所有字符串匹配算法里最知名的一种非kmp算法莫属,很多时候提到字符串匹配我们首先想到的就是kmp算法。但kmp算法是出了名的不好懂,个人也是一直理解不好。而上面知乎高赞的分析我觉得用来理解KMP算法非常形象,对于理解kmp算法的核心原理非常有帮助。
简单总结上面的分析:在模式串和主串匹配过程中,当遇到坏字符后,主串不回退,用已经匹配的字符串的头部匹配其尾部,即前缀匹配后缀,直接回退到最长匹配前缀的尾部位置。
很明显,已匹配的字符串其实就是模式串的一部分,所以如何计算已匹配的字符串的最长匹配,其实可以不涉及主串。而模式串每一部分的最长匹配前缀的尾部位置其实预先计算好,在模式串和主串匹配的过程中直接拿过来用就好。kmp算法就是提前构建一个数组,用来存储模式串中每个前缀的最长可匹配前缀子串的结尾字符的下标。我们把这个数组定义为next数组,很多书还为这个数组起了一个名字叫失效数组(failure function)。
数组的下标是每个前缀结尾字符下标,数组的值是这个前缀的最长可匹配前缀子串的结尾字符的下标。这句话比较难理解,看下面是极客时间王争专栏的例子就应该一看就懂了。

至此,我觉得已经足够从原理上理解kmp算法了,具体实现的细节下面的的C++代码实现已经足够清楚,此处不再赘述。本人理解kmp算法过程非常坎坷,之前自以为理解了其实根本没弄懂,写的博文纯粹也是误人子弟,所以现在重新修改后有种如释重负的感觉。这里,再次感谢知乎的回答的作者和王争老师帮助!
代码实现
#ifndef _KMP_H
#define _KMP_H #include <string>
#include <vector>
#include <list> std::vector<int> getNext(std::string &s) {
int k = -;
int i = ;
std::vector<int> next(s.size(), -);
for (int i = ; i < s.size(); ++i) {
while (k != - && s[k + ] != s[i]) {
k = next[k];
}
if (s[k + ] == s[i]) {
++k;
}
next[i] = k;
}
return next;
} std::list<int> kmp(std::string &text, std::string &s) {
std::list<int> posList;
std::vector<int> next = getNext(s);
int k = -;
for (int i = ; i < text.size(); ++i) {
while (k != - && s[k + ] != text[i]) {
k = next[k];
}
if (s[k + ] == text[i]) {
k++;
}
if (k == s.size() - ) {
posList.push_back(i - k);
k = next[k];
}
}
return posList;
} #endif
kmp算法分析和C++实现的更多相关文章
- KMP算法分析
KMP是一种复杂度较低的字符串比较算法.基本思路是对欲匹配字符串进行预处理,分析当k位匹配时可以后移的位数,所得的数构成该字符串的特征向量. 求特征向量Next int* Next(string p) ...
- poj 2752 Seek the Name, Seek the Fame【KMP算法分析记录】【求前后缀相同的子串的长度】
Seek the Name, Seek the Fame Time Limit: 2000MS Memory Limit: 65536K Total Submissions: 14106 Ac ...
- KMP算法和bfprt算法总结
目录 1 KMP算法 1.1 KMP算法分析 1.2 KMP算法应用 题目1:旋转词 题目2:子树问题 2 bfprt算法 2.1 bfprt算法分析 2.2 bfprt算法应用 1 KMP算法 大厂 ...
- 算法:KMP算法
算法:KMP排序 算法分析 KMP算法是一种快速的模式匹配算法.KMP是三位大师:D.E.Knuth.J.H.Morris和V.R.Pratt同时发现的,所以取首字母组成KMP. 少部分图片来自孤~影 ...
- 字符串匹配:KMP算法
一.原理: KMP算法是由Knuth,Morris,Pratt共同提出的模式匹配算法,其对于任何模式和目标序列,都可以在线性时间内完成匹配查找,而不会发生退化,是一个非常优秀的模式匹配算法.朴素算法( ...
- Java数据结构之字符串模式匹配算法---KMP算法
本文主要的思路都是参考http://kb.cnblogs.com/page/176818/ 如有冒犯请告知,多谢. 一.KMP算法 KMP算法可以在O(n+m)的时间数量级上完成串的模式匹配操作,其基 ...
- 程序员必会算法-KMP算法
KMP算法是一种优秀的字符串匹配算法,字符串匹配的常规算法是一步一步进行移位和比较操作,直至找到完全相匹配的字符串. 下面通过一个例子,为大家仔细说明KMP算法的使用和思路: 问题: 在字符串“DEA ...
- 算法导论————KMP
[例题传送门:caioj1177] KMP模版:子串是否出现 [题意]有两个字符串SA和SB,SA是母串,SB是子串,问子串SB是否在母串SA中出现过.如果出现过输出第一次出现的起始位置和结束位置,否 ...
- 数据结构4_java---顺序串,字符串匹配算法(BF算法,KMP算法)
1.顺序串 实现的操作有: 构造串 判断空串 返回串的长度 返回位序号为i的字符 将串的长度扩充为newCapacity 返回从begin到end-1的子串 在第i个字符之前插入字串str 删除子串 ...
随机推荐
- Amazon Redshift and the Case for Simpler Data Warehouses
Redshift是Amazon一个商业产品上的进化 但并不是技术的进化,他使用的无非都是传统数仓领域的技术 如果说创新,就是大量使用Amazon本身的云服务的云原生架构,大大提升的产品的迭代速度,可维 ...
- JavaWeb_(Struts2框架)拦截器interceptor
此系列博文基于同一个项目已上传至github 传送门 JavaWeb_(Struts2框架)Struts创建Action的三种方式 传送门 JavaWeb_(Struts2框架)struts.xml核 ...
- C++入门经典-例8.8-虚继承
1:以前讲到从CBird类和CFish类派生子类CWaterBird时,在CWaterBird类中将存在两个CAnimal类的复制.那么如何在派生CWaterBird类时使其只存在一个CAnimal基 ...
- How to intercept any postback in a page? - ASP.NET
How to intercept any postback in a page? - ASP.NET There's a couple of things you can do to intercep ...
- ubuntu 18.04 64bit下如何源码编译安装anbox
1. 准备工作 1.1 安装gcc 7.x版本 sudo apt-get install gcc-7 -y 1.2 安装依赖的库及其工具 sudo apt install build-essentia ...
- LC 894. All Possible Full Binary Trees
A full binary tree is a binary tree where each node has exactly 0 or 2 children. Return a list of al ...
- oracle 查看数据库和表命令
1.su – oracle 不是必需,适合于没有DBA密码时使用,可以不用密码来进入sqlplus界面. 2.sqlplus /nolog 或sqlplus system/manager 或./sql ...
- Tomcat 8.5 配置 SSL 证书
前文: 1.以上内容仅支持Linux-Tomcat配置 正文: 说一下我遇到的坑,我使用的服务器是阿里云服务器,阿里云提供的云服务器Tomcat配置SSL是7.0版本,跟8.5出入较大. 以下为阿里提 ...
- 阶段3 3.SpringMVC·_02.参数绑定及自定义类型转换_7 获取Servlet原生的API
控制器里面获取常用的request对象或者Response对象. 想拿到谁, 就在参数上写上谁 再获取servletConetext并输出
- 【JVM学习笔记】双亲委托机制存在的意义
1.可以确保Java核心库的类型安全:所有的Java应用都至少会引用java.lang.Object类,也就是说在运行期,java.lang.Object这个类会被加载到Java虚拟机:如果用户自定义 ...