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. ELK查询和汇总

    查询表明细: ELK的KQL样例,显示时间请选择最近15天: 样例1:查询ol_lc 表增删改查,不是jy2_rw的账号明细 KQL:(ol_lc or oc.ol_lc) and (select o ...

  2. Java并发编程实例--19.在一个锁中使用多个条件

    一个锁可能关联了一个或多个条件.这些条件可以在Condition接口中声名. 使用这些条件的目的是去控制一个锁并且可以检查一个条件是true或false,如果为false,则暂停直到 另一个线程来唤醒 ...

  3. QT - Day 3

    对话框 分类 模态对话框 QDialog dlg(this); dlg.resize(200,100); dlg.exec(); //窗口阻塞 非模态对话框 QDialog *dlg2 = new Q ...

  4. Redis 缓存过期删除/淘汰策略分析

    Redis 缓存过期删除/淘汰策略分析 Redis 缓存删除 Redis 键过期删除,定期删除(主动)和惰性删除(被动) Redis 内存不足时,缓存淘汰策略 key 键过期删除 我们用 redis ...

  5. Kafka本地单实例安装

    下载安装 从国内镜像站点下载并安装Kafka安装包,以下载并安装v3.2.3版本为例. $ tar -xzf kafka_2.12-3.2.3.tgz $ cd kafka_2.12-3.2.3 启动 ...

  6. SSH 客户端

    简介 OpenSSH 的客户端是二进制程序 ssh.它在 Linux/Unix 系统的位置是/usr/local/bin/ssh. Linux 系统一般都自带 ssh,如果没有就需要安装. # Ubu ...

  7. 高效的PDF文字提取技术

    无论是行政法规.学术论文还是企业合同,PDF文档为我们提供了一种便捷.稳定的信息传递方式.然而,从PDF文件中提取文本信息对于数据分析.内容编辑等后续处理来说至关重要. PDF文本提取技术是一种可以从 ...

  8. ASP.NET 跨域配置

    报错信息 The 'Access-Control-Allow-Origin' header is present on the requested resource 解决方案 web.config配置 ...

  9. 【Azure Cloud Service】Cloud Service(Classic) 迁移失败,找不到解决方案怎么办?

    问题描述 很老很老的云服务,在迁移到 Cloud Service(Extended Support)[云服务外延支持] 时,迁移的验证步骤不通过,因为资源中没有包含虚拟网络(Virtual Netwo ...

  10. 【Azure 应用服务】当在Azure App Service的门户上 Log Stream 日志无输出,需要如何操作让其输出Application Logs呢?

    问题描述 在Azure App Service的门户上 Log Stream 日志无输出,需要如何操作让其输出Application Logs呢? 如下图所示: 问题解答 请注意,上图中提示说:App ...