知乎高赞分析

作者:逍遥行

链接:https://www.zhihu.com/question/21923021/answer/37475572
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

角色:

甲: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++实现的更多相关文章

  1. KMP算法分析

    KMP是一种复杂度较低的字符串比较算法.基本思路是对欲匹配字符串进行预处理,分析当k位匹配时可以后移的位数,所得的数构成该字符串的特征向量. 求特征向量Next int* Next(string p) ...

  2. 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 ...

  3. KMP算法和bfprt算法总结

    目录 1 KMP算法 1.1 KMP算法分析 1.2 KMP算法应用 题目1:旋转词 题目2:子树问题 2 bfprt算法 2.1 bfprt算法分析 2.2 bfprt算法应用 1 KMP算法 大厂 ...

  4. 算法:KMP算法

    算法:KMP排序 算法分析 KMP算法是一种快速的模式匹配算法.KMP是三位大师:D.E.Knuth.J.H.Morris和V.R.Pratt同时发现的,所以取首字母组成KMP. 少部分图片来自孤~影 ...

  5. 字符串匹配:KMP算法

    一.原理: KMP算法是由Knuth,Morris,Pratt共同提出的模式匹配算法,其对于任何模式和目标序列,都可以在线性时间内完成匹配查找,而不会发生退化,是一个非常优秀的模式匹配算法.朴素算法( ...

  6. Java数据结构之字符串模式匹配算法---KMP算法

    本文主要的思路都是参考http://kb.cnblogs.com/page/176818/ 如有冒犯请告知,多谢. 一.KMP算法 KMP算法可以在O(n+m)的时间数量级上完成串的模式匹配操作,其基 ...

  7. 程序员必会算法-KMP算法

    KMP算法是一种优秀的字符串匹配算法,字符串匹配的常规算法是一步一步进行移位和比较操作,直至找到完全相匹配的字符串. 下面通过一个例子,为大家仔细说明KMP算法的使用和思路: 问题: 在字符串“DEA ...

  8. 算法导论————KMP

    [例题传送门:caioj1177] KMP模版:子串是否出现 [题意]有两个字符串SA和SB,SA是母串,SB是子串,问子串SB是否在母串SA中出现过.如果出现过输出第一次出现的起始位置和结束位置,否 ...

  9. 数据结构4_java---顺序串,字符串匹配算法(BF算法,KMP算法)

    1.顺序串 实现的操作有: 构造串 判断空串 返回串的长度 返回位序号为i的字符 将串的长度扩充为newCapacity 返回从begin到end-1的子串 在第i个字符之前插入字串str 删除子串 ...

随机推荐

  1. URAL 2092 Bolero 贪心

    C - Bolero Time Limit:1000MS     Memory Limit:65536KB     64bit IO Format:%I64d & %I64u Submit S ...

  2. Uva 10129 Play on Words(欧拉路)

    一些秘密的门包含一个非常有趣的单词拼图.考古学家们必须解决的问题 它打开那门.因为没有其他的方式来打开大门,这个谜是非常重要的 我们. 每扇门上都有大量的磁性板.每一个盘子上都有一个字 它.板块必须以 ...

  3. HGOI 20190828 题解

    Problem A 数学题 设数论函数$f(x)$表示$x(x ∉ Prime)$的次大因数, 给出$l,r$求出$\sum\limits_{i=l,i ∉ Prime} ^r f(i)$ . 对于$ ...

  4. mysql5.7提示密码过期的解决方法

    首先把MySQL关闭 打开一个终端,输入 sudo /usr/local/mysql/bin/mysqld_safe --skip-grant-tables 执行完命令后,再打开一个新的终端 sudo ...

  5. ETL-拉链算法-1

    参考链接:http://blog.csdn.net/nisjlvhudy/article/details/8559518 自己的理解: 主要针对数据的状态变化 表:原表:source 目标表:targ ...

  6. TCP层accept系统调用的实现分析

    inet_csk_accept函数实现了tcp协议accept操作,其主要完成的功能是,从已经完成三次握手的队列中取控制块,如果没有已经完成的连接,则需要根据阻塞标记来来区分对待,若非阻塞则直接返回, ...

  7. SRS之SrsConfig类

    1. 类定义 1.1 SrsConfig 类 /** * the config service provider. * for the config supports reload, so never ...

  8. 【黑马JavaWeb】.1.2反射机制

    文章目录 反射:框架设计的灵魂 获取Class类对象的方式 学习视频:https://www.bilibili.com/video/av47886776?p=10 本来一万行的代码,使用框架以后简化到 ...

  9. 注意机制CBAM

    这是一种用于前馈卷积神经网络的简单而有效的注意模块. 给定一个中间特征图,我们的模块会沿着两个独立的维度(通道和空间)依次推断注意力图,然后将注意力图乘以输入特征图以进行自适应特征修饰. 由于CBAM ...

  10. html页面元素命名参考

    页面结构: (1)页面结构 容器: container 页头:header 内容:content/container 页面主体:main 页尾:footer 导航:nav 侧栏:sidebar 栏目: ...