字符串基础与 KMP
要提到 KMP 算法,首先得提到字符串相关知识。
字符串相关
概念
- 前缀/后缀:这个很容易理解。
- 真前缀/真后缀:就是非原串的前缀/后缀。
- 子串:从原串中选取连续的一段就是一个子串,空串也算子串。
- 任何子串都是一个前缀的后缀/一个后缀的前缀。
- 周期:当满足
时, 是 的周期。 - Border:当一个字符串
,既是 的前缀,又是 的后缀时, 就是 的一个 Border。
性质
- 当一个字符串
为 的 Border 时, 为 的一个周期。 - Border 具有传递性,即当
为 的 Border、 为 的 Border 时,必然有 为 的 Border。 - Border 传递性
:当 为 的 Border、 为 的 Border 时,必然有 为 的 Border。
字符串匹配
模板:P3375 【模板】KMP。
令
给定两个字符串
暴力匹配
我们拿两个指针
在更新时
- 若
,则各自后移,i++, j++;
,当 时, 已经完全匹配,可以推出它的起始位置等。 - 否则,右移
,调整 ,使得 仍然满足,那么该如何调整呢?
Next[] 失配数组
在发生不匹配时,我们需要调整
nxt[i] = max{k | pre(s, k) = suf(pre(s, i), k)}
,即
若
为 的 Border。 。
求解方法
假设
- 需要找到其中最大的
使得 。 - 此时
nxt[i] = k + 1
(即 的最长 Border)。
根据定义和 Border 的传递性,
- 求
就是 开始检查 是否成立,不成立就一直往前找 Next。 然后重复上述判断(找到满足条件的最长 Border)。
Code
void get_fail () { nxt[0] = -1; for (int i = 2, j = 0; i <= m; i++) { while (j >= 0 && t[i] != t[j + 1]) j = nxt[j]; nxt[i] = ++j; } }
求完了失配数组,剩下就好办了。
KMP Code
for (int i = 1, j = 0; i <= n; i++) { while (j >= 0 && s[i] != t[j + 1]) j = nxt[j]; j++; if (j == m) { cout << i - j + 1 << '\n'; } }
模板完整代码
#include <bits/stdc++.h> using namespace std; const int N = 1e6 + 10; string s, t; int n, m, nxt[N]; void get_fail () { nxt[0] = -1; for (int i = 2, j = 0; i <= m; i++) { while (j >= 0 && t[i] != t[j + 1]) j = nxt[j]; nxt[i] = ++j; } } int main () { ios::sync_with_stdio(0), cin.tie(0); cin >> s >> t, n = s.size(), m = t.size(), s = " " + s, t = " " + t; get_fail(); for (int i = 1, j = 0; i <= n; i++) { while (j >= 0 && s[i] != t[j + 1]) j = nxt[j]; j++; if (j == m) { cout << i - j + 1 << '\n'; } } for (int i = 1; i <= m; i++) { cout << nxt[i] << ' '; } return 0; }
时间复杂度:
字符串的周期
性质里有,而字符串
失配树
顾名思义,就是将
这棵树有什么用呢?树上的两个节点
而一个节点
本文作者:wnsyou の blog
本文链接:https://www.cnblogs.com/wnsyou-blog/p/KMP.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步