KMP(Knuth-Morris-Pratt字符串查找算法)

KMP 算法是可以快速在文本串 s 中找到模式串 a 的算法。

Part 1:幼稚的算法

首先思考我们在暴力匹配模式串时的思路:

<img src="https://pic2.zhimg.com/v2-5362ed0fdda9727210ba82e0765fef51_b.jpg" data-caption="" data-size="normal" data-rawwidth="577" data-rawheight="641" class="origin_image zh-lightbox-thumb" width="577" data-original="https://pic2.zhimg.com/v2-5362ed0fdda9727210ba82e0765fef51_r.jpg"/>

一旦有一位失配,就需要整个回溯,导致时间复杂度超标。

而 KMP 算法主要就是优化了这个回溯的问题。

一个人有多强不在于他能在顺境时走得多远,而在于他在逆境时能多久找回曾经的自己。

KMP 算法

首先我们要考虑让某个点不回溯,再优化另一个点的回溯过程。

KMP 给出了这样的一种方式:

设 i 表示文本串的位置,j 表示模式串的位置,我们让 i 不动,j 回溯到最合适的位置,这个位置,我们记为PMT(Partial Match Table,部分匹配表)。

PMT 数组的含义,也可以这样表示:

1 到 j 子串的最长公共前后缀长度。

就像这样:

<img src="https://pic3.zhimg.com/v2-8c8ab6a4d4e958a9851d880bb9162a36_b.jpg" data-caption="" data-size="small" data-rawwidth="273" data-rawheight="114" class="content_image" width="273"/>

当然,最长前后缀是可以重叠的:

<img src="https://pic1.zhimg.com/v2-e9e845ce679e832a8ddc4783a9d92450_b.jpg" data-caption="" data-size="small" data-rawwidth="163" data-rawheight="89" class="content_image" width="163"/>

那就有个问题,难道最长的不是整个串吗?所以为了避免卡 bug,PMT 要求这个公共前后缀的长度要小于子串长度。

我们在考虑一开始那个发生失配的情况,用 KMP 算法就可以变成这样:

<img src="https://pic4.zhimg.com/v2-272ca60e1a83422590205936d0cea2a7_b.jpg" data-caption="" data-size="normal" data-rawwidth="571" data-rawheight="623" class="origin_image zh-lightbox-thumb" width="571" data-original="https://pic4.zhimg.com/v2-272ca60e1a83422590205936d0cea2a7_r.jpg"/>

实际上我们没有移动 i,只是让 j 变成了 pmt[j-1]。
如果这一位继续失配,那么 j 又变成了 pmt[j-1]。

反复如此,直到不得不移动 i 为止。

那么代码可以写成这样:

for(int i=0,j=0;i<s.size();i++){
while(j && s[i] != a[j])
j = pmt[j-1];
if(s[i] == a[j]) j++;
if(j == a.size())
j = pmt[j-1];
}

对于每一位首先处理失配的情况,然后判断是否能匹配当前位置,特别的是当 j 匹配完后(匹配成功),就需要准备下一次匹配,也可以理解为 j 的下一位(空)和 i 的下一位失配了。

不过我们上面的代码是假设 pmt 数组已经求出,别忘了求出 pmt 本身也不简单。

一个精妙的方法是进行模式串的自匹配。首先将模式串错开一位,然后和自己匹配一次,这样每次匹配的最大长度就刚好是公共前后缀的长度!

<img src="https://pic4.zhimg.com/v2-80eef28497bccf10ebf0f4dcf2a9d4f7_b.jpg" data-caption="" data-size="normal" data-rawwidth="760" data-rawheight="751" class="origin_image zh-lightbox-thumb" width="760" data-original="https://pic4.zhimg.com/v2-80eef28497bccf10ebf0f4dcf2a9d4f7_r.jpg"/>

代码如下:

#include <bits/stdc++.h>
using namespace std; int main(){
string a;
cin>>a;
int pmt[114]={0};
for(int i=1,j=0;i<a.size();i++){
while(j && a[i] != a[j])
j = pmt[j-1];
if(a[i] == a[j]) j++;
pmt[i] = j;
}
for(int i=0;i<a.size();i++){
cout<<pmt[i]<<' ';
}
return 0;
}

例题和代码

P3375 【模板】KMP 字符串匹配

border 其实就是 pmt 数组。

const int N=1000005;
int pmt[N];
int main(){
ios_base::sync_with_stdio(false);cin.tie(0);cout.tie(0);
string s,a;
cin>>s>>a;
for(int i=1,j=0;i<a.size();i++){
while(j && a[i] != a[j])
j = pmt[j-1];
if(a[i] == a[j]) j++;
pmt[i] = j;
}
for(int i=0,j=0;i<s.size();i++){
while(j && s[i] != a[j])
j = pmt[j-1];
if(s[i] == a[j]) j++;
if(j == a.size()){
cout<<i+1-(a.size()-1)<<endl;
j = pmt[j-1];
}
}
for(int i=0;i<a.size();i++){
cout<<pmt[i]<<' ';
}
return 0;
}

算法学习笔记【6】| KMP 算法的更多相关文章

  1. 【算法学习笔记】Meissel-Lehmer 算法 (亚线性时间找出素数个数)

    「Meissel-Lehmer 算法」是一种能在亚线性时间复杂度内求出 \(1\sim n\) 内质数个数的一种算法. 在看素数相关论文时发现了这个算法,论文链接:Here. 算法的细节来自 OI w ...

  2. 算法学习笔记:Kosaraju算法

    Kosaraju算法一看这个名字很奇怪就可以猜到它也是一个根据人名起的算法,它的发明人是S. Rao Kosaraju,这是一个在图论当中非常著名的算法,可以用来拆分有向图当中的强连通分量. 背景知识 ...

  3. 算法学习笔记:Tarjan算法

    在上一篇文章当中我们分享了强连通分量分解的一个经典算法Kosaraju算法,它的核心原理是通过将图翻转,以及两次递归来实现.今天介绍的算法名叫Tarjan,同样是一个很奇怪的名字,奇怪就对了,这也是以 ...

  4. Miller-Rabin 与 Pollard-Rho 算法学习笔记

    前言 Miller-Rabin 算法用于判断一个数 \(p\) 是否是质数,若选定 \(w\) 个数进行判断,那么正确率约是 \(1-\frac{1}{4^w}\) ,时间复杂度为 \(O(\log ...

  5. 算法笔记之KMP算法

    本文是<算法笔记>KMP算法章节的阅读笔记,文中主要内容来源于<算法笔记>.本文主要介绍了next数组.KMP算法及其应用以及对KMP算法的优化. KMP算法主要用于解决字符串 ...

  6. 算法学习笔记(20): AC自动机

    AC自动机 前置知识: 字典树:可以参考我的另一篇文章 算法学习笔记(15): Trie(字典树) KMP:可以参考 KMP - Ricky2007,但是不理解KMP算法并不会对这个算法的理解产生影响 ...

  7. C / C++算法学习笔记(8)-SHELL排序

    原始地址:C / C++算法学习笔记(8)-SHELL排序 基本思想 先取一个小于n的整数d1作为第一个增量(gap),把文件的全部记录分成d1个组.所有距离为dl的倍数的记录放在同一个组中.先在各组 ...

  8. GMM高斯混合模型学习笔记(EM算法求解)

    提出混合模型主要是为了能更好地近似一些较复杂的样本分布,通过不断添加component个数,能够随意地逼近不论什么连续的概率分布.所以我们觉得不论什么样本分布都能够用混合模型来建模.由于高斯函数具有一 ...

  9. Manacher算法学习笔记 | LeetCode#5

    Manacher算法学习笔记 DECLARATION 引用来源:https://www.cnblogs.com/grandyang/p/4475985.html CONTENT 用途:寻找一个字符串的 ...

  10. Johnson算法学习笔记

    \(Johnson\)算法学习笔记. 在最短路的学习中,我们曾学习了三种最短路的算法,\(Bellman-Ford\)算法及其队列优化\(SPFA\)算法,\(Dijkstra\)算法.这些算法可以快 ...

随机推荐

  1. 阿里数据库SRE(转)

    SRE的职责划分却不尽相同,那么SRE究竟在做什么? SRE的职责 SRE主要负责Google所有核心业务系统的可用性.性能.容量相关的事情,根据<Site Reliability Engine ...

  2. Java并发编程实例--14.在一个同步类中安排独立属性

    当你使用synchronized关键字去保护一个代码块时,你必须传入一个对象的引用. 正常来讲,你讲使用this关键字去引用执行这个方法的对象,但是你可以使用其他对象的引用. 通常的,这些对象将会是专 ...

  3. 机器学习策略篇:详解为什么是ML策略?(Why ML Strategy?)

    为什么是ML策略? 从一个启发性的例子开始讲,假设正在调试的猫分类器,经过一段时间的调整,系统达到了90%准确率,但对的应用程序来说还不够好. 可能有很多想法去改善的系统,比如,可能想去收集更多的训练 ...

  4. 学习go语言编程之面向对象

    类型系统 类型系统是指一个语言的类型体系结构,一个典型的类型系统通常包含如下基本内容: 基础类型,如:byte.int.bool.float等 复合类型,如:数组.结构体.指针等 可以指向任意对象的类 ...

  5. 使用Java线程同步工具类CountDownLatch

    java.util.concurrent.CountDownLatch是Java并发并发编程中的线程同步工具类,基于AQS(java.util.concurrent.locks.AbstractQue ...

  6. 混合类Mixins介绍

    介绍 混合类是封装了一些通用行为的基类,旨在重用代码.通常,混合类本身并没有什么用,仅扩展这种类也行不通 因为在大多数情况下,它都依赖于其它类中定义的方法和属性.通过多继承,可将混合类与其它类一起使用 ...

  7. nginx中使用perl脚本来定制一些请求转发等等

    http://t.zoukankan.com/carriezhangyan-p-9359708.html https://blog.csdn.net/weixin_28917223/article/d ...

  8. fatal: bad object refs/remotes/origin/xxx

    解决方案: 1.项目的.git文件内的目录.git/logs/refs/remotes/origin/,删除该错误的本地远程分支: 2.执行git pull --rebase即可 类似错误信息例子: ...

  9. 【Azure Developer】Python 获取 Azure 中订阅(subscription)信息,包含ID, Name等

    问题描述 在Azure AD中注册一个Applicaiton后,对其进行授权,能够查看所有订阅的ReadOnly权限,然后,是否可以同通过Python代码,在完成Authorization后(使用Cl ...

  10. 基于 Nebula Graph 构建百亿关系知识图谱实践

    本文首发于 Nebula Graph Community 公众号 一.项目背景 微澜是一款用于查询技术.行业.企业.科研机构.学科及其关系的知识图谱应用,其中包含着百亿级的关系和数十亿级的实体,为了使 ...