重学 KMP 小记
重学 KMP 小记
前言
KMP 这个东西赛时用到的几率很小(虽然圣人说概率不小、也不是很大),但是如果一旦考字符串类的题又极可能考匹配问题。当时掌握得也是一知半解,所以现在来重学来了。
情境引入
现实中我们会遇到类似的问题:
给你一篇报道,让你找一找这篇报道中有没有出现某个人的名字。
形式化地,可以说:
给你文本串 ,和模式串 ,判断 是否为 的子串。
这个问题我们暴力地想,可以用两个指针 , 分别表明现在匹配到 , 的哪个位置了(,)。如果 ,则 、。相当于是推翻重来。
有没有优美一点的算法呢?答案是有的,就是我们的主角——KMP。
算法概要
我们在暴力的时候,如果一旦失配,模式串的指针 就又从头开始,这显然是非常浪费的。所以我们如果想降低时间复杂度,就要从这里入手。
首先我们定义一个数组 ,其满足:。 表示 组成的子串。当然这个 有很多种情况,我们储存的是子串最长的情况。说白了这两部分子串就是 的最长公共前后缀。
特别地,。
接下来就可以引入 KMP 了,算法流程如下:
- 如果 与 匹配成功,即相同,就 ,,继续匹配。
- 如果失配,则令 不动,。这意味着 不变,将整个 向右移动了 位。这个值肯定是大于等于 的。
这样就没了。
现在来分析一下这个 KMP 是怎么减少浪费的。
当 匹配到 位时,说明前面都是和 相同的。如果此时失配了,暴力的思想是相当于直接把 向右平移一位,然后重新比较。这样显然没有前途。KMP 是怎么做的呢?KMP 的思想是:“既然我这个 里可能有公共前后缀,如果有的话,为什么我不直接把 向右平移至这个最长公共前后缀相同的部分呢?”。
画个草图理解一下:
图中蓝色的部分都相同。
如何预处理 数组
考虑递推。现假设 的元素都已求出。
算法过程就很简单了:
- 如果
str[i]==str[next[i-1]+1]
,则next[i]=next[i-1]+1
。 - 否则判断
str[i]
与str[next[next[i-1]]+1]
是否相等。 - 再否则,判断
str[i]
与str[next[next[next[i-1]]]+1]
是否相等。 - 回环往复,直至相等或 的值为 为止。
void getnxt() { int j=0; for(int i=2;i<=m;i++) { while(j&&b[j+1]!=b[i]) j=nxt[j]; if(b[j+1]==b[i]) j++; nxt[i]=j; } }
例题展现
#include<bits/stdc++.h> using namespace std; #define int long long const int MAXN=1e6+5; int n,m; char a[MAXN]; char b[MAXN]; int nxt[MAXN]; void getnxt() { int j=0; for(int i=2;i<=m;i++) { while(j&&b[j+1]!=b[i]) j=nxt[j]; if(b[j+1]==b[i]) j++; nxt[i]=j; } } void kmp() { for(int i=1,j=0;i<=n;i++) { while(j&&a[i]!=b[j+1]) j=nxt[j]; if(b[j+1]==a[i]) j++; if(j==m) { printf("%lld\n",i-m+1); j=nxt[j]; } } } signed main() { scanf("%s%s",a+1,b+1); n=strlen(a+1),m=strlen(b+1); getnxt(); kmp(); for(int i=1;i<=m;i++) printf("%lld ",nxt[i]); return 0; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效