算法学习笔记(16)——KMP
KMP
KMP 算法,又称模式匹配算法,能够在线性时间内判定字符串 是否为字符串 的子串。并求出字符串 在字符串 中各次出现的位置。
首先,一个 的朴素做法是,尝试枚举字符串 中的每个位置 ,把字符串 与字符串 的后缀 对齐,向后扫描逐一比较 与 , 与 ,...... 是否相等。我们把这种比较过程称为 与 尝试进行“匹配”。
KMP算法能够更高效、更准确地处理这个问题,并且能够为我们提供一些额外的信息。详细地讲,KMP算法分为两步。
- 对字符串 进行自我“匹配”,求出一个数组 ,其中 表示“ 中以 结尾的非前缀子串”与“ 的前缀”能够匹配的最长长度,即:特别的,当不存在这样的 时,令 。
- 对字符串 与 进行匹配,求出一个数组 ,其中 表示“ 中以 结尾的子串”与“ 的前缀”能够匹配的最大长度,即:
下面介绍 数组的求法:
1. 初始化 next[1] = j = 0,假设 next[1 ~ i-1] 已求出,下面求解 next[i]
2. 不断尝试扩展匹配长度 j,如果扩展失败(下一个字符不相等),令 j 变为 next[j],直至 j 为0(应该重新从头开始匹配)
3. 如果能够扩展成功,匹配长度 j 就增加 1。next[i] 的值就是 j。
求解 与求解 的过程基本一致,具体参考模板代码。
#include <iostream>
using namespace std;
const int N = 1e5 + 10, M = 1e6 + 10;
int n, m;
char p[N]; // 模版串
char s[M]; // 模式串
int ne[N]; // next数组
int main()
{
// 注意,此处从下标1开始读入字符串,方便后续操作
cin >> n >> p + 1 >> m >> s + 1;
// 1. 预处理next数组,默认next[1] = 0,从2开始处理
for (int i = 2, j = 0; i <= n; i ++ ) {
// 当j不是第一个匹配点且匹配不成功时,从下一候选项尝试
while (j && p[i] != p[j + 1]) j = ne[j];
// 匹配成功
if (p[i] == p[j + 1]) j ++;
ne[i] = j;
}
// 2. 匹配
for (int i = 1, j = 0; i <= m; i ++ ) {
while (j && s[i] != p[j + 1]) j = ne[j];
if (s[i] == p[j + 1]) j ++;
// 此时就是 P 在 S 中的某一次出现
if (j == n) {
// 输出匹配成功的起始位置
cout << i - n << ' ';
// 从下一候选项继续尝试匹配
j = ne[j];
}
}
return 0;
}
在上面代码的while
循环中,j
的值不断减小,j=ne[j]
的执行次数不会超过每层for
循环开始时j
的值与while
循环结束时j
的值之差。而在每层for
循环中,j
的值至多增加 。因为j
始终非负,所以在整个计算过程中,j
减小的幅度总和不会超过j
增加的幅度总和。故j
的总变化次数至多为 。整个算法的时间复杂度为 。这样只需遍历一次 就可以得到 的所有匹配位置。
- 计算Partial_Table(或者说是计算模式串的最长公共前缀后缀长度列表)时的比较次数介于[m,2m],假设m是模式串的长度.
- 比较模式串和子串时比较次数介于[n,2n],最坏情形形如T="aaaabaaaab",P="aaaaa".
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】