Z 算法/拓展 KMP 详解
引入
先来看几个问题:
什么是 Z 算法呢?
Z 算法(Z-Algorithm,又叫拓展 KMP 或 exKMP)是一种字符串匹配算法,可以在
为什么要用 Z 算法?
暴力进行匹配是
接下来是一些约定:
表示 - z 函数用
表示,原字符串用 表示 - 下标从
开始
算法流程
定义
定义: 对于任意
例如:
, ,因为
,
,
并且,对于任意
这个概念有什么用呢?一会求 Z 函数时会用到。
如何求解
求
我们知道暴力求
我们维护右端点最右的 Z-Box 的左右端点,记为
考虑如何求
令
由图可知,
那么如果
但是如果
其实不一定,看下面这张图:
图中
无关紧要的证明:
其实
不可能大于 ,因为如果大于,那么就会变成下面这样:
那么粉色和橙色的前面部分是相等的,那么红色就可以继续往后延伸,与前面
的定义矛盾。
时间复杂度
因为
代码
int z[N];
void z_algorithm(const char *s) { // 下标从 1 开始
int l = 0, r = 0;
int n = strlen(s + 1);
for(int i = 2; i <= n; i++) {
if(i <= r) z[i] = std::min(z[i - l + 1], r - i + 1);
while(i + z[i] <= n && s[i + z[i]] == s[z[i] + 1]) z[i]++;
if(i + z[i] > r) l = i, r = i + z[i] - 1;
}
}
应用
例题 1
给出两个字符串 s, t,求出所有的位置 x,满足
。
其中,s, t 仅包含大小写字母。
可以设一个字符
然后看
也可以按照 Z 算法类似的方式直接算。(设
int e[N];
void exkmp(const char *s, const char *t) { // 下标从 1 开始
int l = 0, r = 0;
int n = strlen(s + 1);
for(int i = 2; i <= n; i++) {
if(i <= r) e[i] = std::min(z[i - l + 1], r - i + 1);
while(i + e[i] <= n && s[i + e[i]] == t[e[i] + 1]) e[i]++;
if(i + e[i] > r) l = i, r = i + e[i] - 1;
}
}
例题 2
#include <cstdio>
#include <algorithm>
#include <cstring>
typedef long long LL;
const int N = 2e7 + 5;
char a[N], b[N];
int z[N];
void z_algorithm(const char *s) { // 下标从 1 开始
int l = 0, r = 0;
int n = strlen(s + 1);
for(int i = 2; i <= n; i++) {
if(i <= r) z[i] = std::min(z[i - l + 1], r - i + 1);
while(i + z[i] <= n && s[i + z[i]] == s[z[i] + 1]) z[i]++;
if(i + z[i] > r) l = i, r = i + z[i] - 1;
}
}
int e[N];
void exkmp(const char *s, const char *t) { // 下标从 1 开始
int l = 0, r = 0;
int n = strlen(s + 1);
for(int i = 2; i <= n; i++) {
if(i <= r) e[i] = std::min(z[i - l + 1], r - i + 1);
while(i + e[i] <= n && s[i + e[i]] == t[e[i] + 1]) e[i]++;
if(i + e[i] > r) l = i, r = i + e[i] - 1;
}
}
int main() {
scanf("%s%s", a + 1, b + 1);
int la = strlen(a + 1), lb = strlen(b + 1);
z_algorithm(b);
z[1] = lb;
LL ans = 0;
for(int i = 1; i <= lb; i++) ans ^= (LL)i * (z[i] + 1);
printf("%lld\n", ans);
exkmp(a, b);
for(int i = 1; i <= lb; i++) if(a[i] == b[i]) e[1] = i; else break;
ans = 0;
for(int i = 1; i <= la; i++) ans ^= (LL)(i) * (e[i] + 1);
printf("%lld\n", ans);
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】