1. KMP简介

kmp算法,是一种线性字符串匹配(父子串为 root,子子串为 s),由 D.E.Knuth,J.H.Morris 和 V.R.Pratt 提出的,因此人们称它为KMP算法。

2. 暴力思路

举个样例吧。

下标 1 2 3 4 5 6 7 8 9
root a b c a a b c a b
s a b c a b

拿到这个串,我们让两个指针 \(l,r\) 分别指向 \(root\) 和 \(s\)。

共 \(len(root)\) 轮,对于第 \(i\) 轮,让 \(l\gets i,r\gets 1\),并执行按顺序执行以下操作:

  • 如果 \(r=len(s)\) 则找到 \(s\)。
  • 如果 \(r\ne len(s)\) 且 \(root_l=s_r\),则 \(l\gets l+1,r\gets r+1\),并重复该操作。
  • 如果 \(root_l\ne s_r\),则跳过该阶段。

就拿样例而言,对于第 \(1\) 轮,\(l=1,r=1\),发现 \(root_1=s_1\),则 \(l+1,r+1\)。当 \(l=5,r=5\) 时,\(root_5\ne s_5\),则进行第2轮,\(l=2,r=1\)......

你会发现,一旦中途不符合条件,已知的信息最会浪费。那有没有不浪费已知信息的算法呢?

答案是有的,那就是烤馍片算法。

3. KMP算法

3.1 过程

再来举个样例:

下标 1 2 3 4 5 6 7 8 9 10 11 12
root A B C D A B C D A B C E
s A B C D A B C E

我们匹配好前面八位的时候,发现第九位不对,我们就移动 \(s\),根据前面已知的,我们就可以像下面这样:

下标 1 2 3 4 5 6 7 8 9 10 11 12
root A B C D A B C D A B C E
s A B C D A B C E

直接向后移动四位,然后我们的 \(l\) 继续从第五位开始,直到匹配成功。

注意:我们都是判断 \(l\) 和 \(r\) 下一个位置是否为相同。

前缀串与后缀串

abcde

前缀串:a,ab,abc,abcd,abcde

后缀串:e,de,cde,bcde,abcde

其实,\(r\) 只是回跳到使得 \(root\) 前 \(l\) 段的后缀与 \(s\) 前 \(r\) 段的前缀相等的最长长度的位置(除了自己),有点长,大家可以分开理解。

例如,当 \(l=8,r=8\) 时,下一个位置不一样,则将 \(r\) 回退到 \(3\),因为 \(root\) 前 \(8\) 个中的倒数 \(3\) 个与 \(s\) 前 \(4\) 个中的前面 \(3\) 个一样都是 \(ABC\),所以回退到 \(3\)。

这里,我们为了预处理,简化一下,来发下一下性质。还是这个例子:

下标 1 2 3 4 5 6 7 8 9 10 11 12
root A B C D \(\red{A}\) \(\red{B}\) \(\red{C}\) D A B C E
s \(\green{A}\) \(\green{B}\) \(\green{C}\) D \(\purple{A}\) \(\purple{B}\) \(\purple{C}\) E

我们要用到的是红色和绿色的字母,这里就是最长公共前后缀。但是,大家想一想,紫色部分和红色部分是不是一模一样的呀,因为指针能到 \(7\),肯定是因为紫色部分和红色部分一样。

那这样子,紫色和红色一样,红色和绿色一样,那绿色就和紫色一样!这样,我们就只需要预处理 \(s\) 就可以了。

3.2 具体实现

到这里,我们可以写出伪代码(\(next\) 为 \(s\) 的最长公共前后缀):

if root[l] = s[r] then
l <- l + 1, r <- r + 1
else then
r <- next[r]

那怎么预处理 \(next\) 呢?再举一个例子:

下标 1 2 3 4 5 6 7 8 9 10 11
s a b a b \(\red{e}\) f a b a b _
next[0]=0
next[1]=0
next[2]=0
next[3]=1
next[4]=2
next[5]=0
next[6]=0
next[7]=1
next[8]=2
next[9]=3
next[10]=4

我们试着用动态规划的思想,已知 \(next_{1\sim10}\),求 \(next_{11}\)。

已知:\(s_{1\sim4}=s_{7\sim10}\)。

  • 如果 \(s_5=s_{11}=e\),那么 \(next_{11}=next_{10}+1\)。
  • 如果 \(s_{11}=a\),那么,取次长公共前后缀,就是 \(ab\),长度为 \(2\),就是 \(next[next[10]]\),刚好可以与 \(a\) 搭配,形成 \(aba\),则 \(next_{11}=next_{next_{10}}+1=3\)。
  • 如果 \(s_{11}=x\),什么都不是,那就只能是 \(0\)了。可以转化为不断地取次长公共前后缀,但是都不符合,最后变成 \(0\) 了而已。

综上,我们得出,只要 \(s_{i+1}\ne s_{r+1}\),就让 \(r\gets next_r\),前提是 \(r\ne0\)。

最后就让 \(next_{i+1}=next_{r}+1\) 就行咯。

代码:

inline void init() {
nex[0] = nex[1] = 0; // 初始化
int m = t.size();
for (int i = 1, j = 0; i < m; i++) { // 求 nex[i+1]
while (j && s[i + 1] != s[j + 1]) j = nex[j]; // 取次长
if (s[i + 1] == s[j + 1]) j++; // 为什么不统一+1?因为上面可能是因为j=0而终止的。
nex[i + 1] = j;
}
return;
}

例题

求出现位置与 \(next\) 数组。

代码:

// Problem: P3375 【模板】KMP
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P3375
// Memory Limit: 512 MB
// Time Limit: 1000 ms
//
// Powered by CP Editor (https://cpeditor.org) /*+ Nimbunny +*/ #include <bits/stdc++.h>
#define endl '\n'
#define pi pair<int, int>
// #define int long long
// #pragma GCC optimize(2) using namespace std;
const int INF = INT_MAX;
const int mod = 1e9 + 7;
const int N = 1e6 + 10;
string root, s;
int n, m, nex[N]; inline int read() {
int x;
cin >> x;
return x;
} inline void init() {
nex[0] = nex[1] = 0; // 初始化
for (int i = 1, j = 0; i < m; i++) { // 求 nex[i+1]
while (j && s[i + 1] != s[j + 1]) j = nex[j]; // 取次长
if (s[i + 1] == s[j + 1]) j++; // 为什么不统一+1?因为上面可能是因为j=0而终止的。
nex[i + 1] = j;
}
return;
} signed main() {
cin.tie(nullptr)->sync_with_stdio(false);
cin >> root >> s;
n = root.size(), m = s.size();
root = "#" + root, s = "#" + s;
init();
for (int l = 0, r = 0; l < n; l++) {
while (r && root[l + 1] != s[r + 1]) r = nex[r];
if (root[l + 1] == s[r + 1]) r++;
if (r == m)
cout << l + 1 - m + 1 << endl; // 因为l还没有+1,所以l要先+1
}
for (int i = 1; i <= m; i++) cout << nex[i] << " ";
return 0;
}

3.3 时间复杂度

鸽子咯咯咯子鸽。

[学习笔记] KMP算法——烤馍片(超详细)的更多相关文章

  1. 学习笔记-KMP算法

    按照学习计划和TimeMachine学长的推荐,学习了一下KMP算法. 昨晚晚自习下课前粗略的看了看,发现根本理解不了高端的next数组啊有木有,不过好在在今天系统的学习了之后感觉是有很大提升的了,起 ...

  2. [一本通学习笔记] KMP算法

    KMP算法 对于串s[1..n],我们定义fail[i]表示以串s[1..i]的最长公共真前后缀. 我们首先考虑对于模式串p,如何计算出它的fail数组.定义fail[0]=-1. 根据“真前后缀”的 ...

  3. 【学习笔记】:JavaScript基础知识超详细总结!

    目录 一.JavaScript的实现 二.JavaScript语言的特点 三.JS与HTML如何结合 四.JS中的数据类型 四.JS的原始数据类型 2.JS的引用数据类型 五.JS引用数据类型之函数 ...

  4. [ML学习笔记] XGBoost算法

    [ML学习笔记] XGBoost算法 回归树 决策树可用于分类和回归,分类的结果是离散值(类别),回归的结果是连续值(数值),但本质都是特征(feature)到结果/标签(label)之间的映射. 这 ...

  5. 【Redis】命令学习笔记——键(key)(20个超全字典版)

    安装完redis和redis-desktop-manager后,开始学习命令啦!本篇基于redis 4.0.11版本,从对键(key)开始挖坑! 准备工作,使用db1(默认db0,由于之前练习用db0 ...

  6. 学习笔记 - Manacher算法

    Manacher算法 - 学习笔记 是从最近Codeforces的一场比赛了解到这个算法的~ 非常新奇,毕竟是第一次听说 \(O(n)\) 的回文串算法 我在 vjudge 上开了一个[练习],有兴趣 ...

  7. 算法笔记--KMP算法 && EXKMP算法

    1.KMP算法 这个博客写的不错:http://www.cnblogs.com/SYCstudio/p/7194315.html 模板: next数组的求解,那个循环本质就是如果相同前后缀不能加上该位 ...

  8. 来去学习之---KMP算法--next计算过程

    一.概述 KMP算法是一种字符串匹配算法,比如现有字符串 T:ABCDABCDABCDCABCDABCDE, P:ABCDABCDE P字符串对应的next值:[0,0,0,0,1,2,3,4,0] ...

  9. 学习笔记——EM算法

    EM算法是一种迭代算法,用于含有隐变量(hidden variable)的概率模型参数的极大似然估计,或极大后验概率估计.EM算法的每次迭代由两步组成:E步,求期望(expectation):M步,求 ...

  10. 数据挖掘学习笔记--AdaBoost算法(一)

    声明: 这篇笔记是自己对AdaBoost原理的一些理解,如果有错,还望指正,俯谢- 背景: AdaBoost算法,这个算法思路简单,但是论文真是各种晦涩啊-,以下是自己看了A Short Introd ...

随机推荐

  1. 我的get请求为什么没有乱码??

    请求的方式有GET和POST两种放方式.这里主要说明GET请求的中文问题. 傻傻的我以为GET不能提交中文,需要改编码,但是我错了.改了编码反而乱码了??? 原因: Tomcat8 以后tomcat的 ...

  2. 【代码】Python3|Requests 库怎么继承 Selenium 的 Headers (2024,Chrome)

    本文使用的版本: Chrome 124 Python 12 Selenium 4.19.0 版本过旧可能会出现问题,但只要别差异太大,就可以看本文,因为本文对新老版本都有讲解. 文章目录 1 难点解析 ...

  3. helm,efk日志系统

    helm:存放配单清单的   chart图表 chart仓库 chart,helm-->Tiller-->api server -->kube_cluster chart---> ...

  4. vue3 基础-生命周期函数

    在 vue 中, 生命周期函数可理解为 "在某个时刻, 会自动执行的函数". 先直观感受一下图示. 一共就八个: <!DOCTYPE html> <html la ...

  5. 构建现代交互式平台:CodeBuddy如何简化复杂系统开发

    我正在参加CodeBuddy「首席试玩官」内容创作大赛,本文所使用的 CodeBuddy 免费下载链接:腾讯云代码助手 CodeBuddy - AI 时代的智能编程伙伴 交互式平台的架构挑战 构建现代 ...

  6. C#中扩展方法无法获得多态性的行为

    在C#中,扩展方法(Extension Methods)是一种用于给现有类型添加新方法的技术.但是,扩展方法无法实现多态性的行为,因为它们是静态方法,它们的行为是在编译时确定的,而不是在运行时. 多态 ...

  7. 计算机图形学——Games101深度解析_第二章

    三维旋转的符号问题 旋转矩阵的符号差异源于坐标系的手系规则和旋转方向定义. 首先是我们最常规的绕着z轴旋转,这是右手系下的标准定义,符合"x轴转向y轴"的正方向. \[\mathb ...

  8. Mysql 修改、删除字段默认值

    问题描述: 建表的时候,某个表中的字段设置了默认值,后期发生需求变更,不需要提供默认值,或者需要改为其它默认值. 问题解决: alter table 表名 alter column 字段名 drop ...

  9. Spring 注解之 @EnableTransactionManagement:Spring Boot 事务配置

    Spring Boot 开启声明式事务支持 所有的数据访问技术都有事务处理机制,这些技术提供了API用来开启事务.提交事务以完成数据操纵,或者在发生错误的时候回滚数据.Spring支持声明式事务,这是 ...

  10. Django Web应用开发实战第十六章

    一.即时通信 - AJAX技术:通过AJAX实现网页与服务器的无刷新交互,在网页上每隔一段时间就通过AJAX从服务器中获取数据.然后将数据更新并显示在网页上,这种方法简单明了,实时性不高. - Com ...