当前位置: 首页 > news >正文

kmp算法

前缀函数

π[i]=max⁡k=0,⋯,i{k∣s[0,⋯,k−1]==s[i−(k−1),⋯,i]}\pi\left[i\right] = \max\limits_{k = 0,\cdots, i}\left\{k|s\left[0,\cdots,k-1\right] == s\left[i-\left(k-1\right) ,\cdots, i\right]\right\} π[i]=k=0,,imax{ks[0,,k1]==s[i(k1),,i]}
简单来说就是最长的相等的真前缀与真后缀的长度

字符串前缀函数求法

我们要求整个字符串每一个位置的前缀函数

最暴力的做法就是枚举,但是这样时间复杂度O(n3)O\left(n^3\right)O(n3)

优化

优化1
计算π[i+1]\pi\left[i+1\right]π[i+1]的时候,不需要从iii枚举到000,可以从π[i]+1\pi\left[i\right] +1π[i]+1枚举到000
也就是说前缀函数至多增加1
证明:
由定义s[0,⋯,π[i]−1]==s[i−(π[i]−1),⋯,i]s\left[0,\cdots,\pi\left[i\right]-1\right] == s\left[i-\left(\pi\left[i\right]-1\right) ,\cdots, i\right]s[0,,π[i]1]==s[i(π[i]1),,i]
并且∀k≥1,s[0,⋯,π[i]−1+k]≠s[i−(π[i]−1)−k,⋯,i]\forall k \ge 1,s\left[0,\cdots,\pi\left[i\right]-1+k\right] \neq s\left[i-\left(\pi\left[i\right]-1\right)-k ,\cdots, i\right]k1,s[0,,π[i]1+k]=s[i(π[i]1)k,,i]

如果π[i+1]=π[i]+1+k\pi\left[i+1\right] = \pi\left[i\right] +1 + kπ[i+1]=π[i]+1+k,其中k≥1k\ge 1k1
那么意味着s[0,⋯,π[i]+k]==s[i+1−(π[i]+k+1−1),⋯,i+1]s\left[0,\cdots,\pi\left[i\right] + k\right] == s\left[i + 1-\left(\pi\left[i\right] + k +1-1\right) ,\cdots, i+1\right]s[0,,π[i]+k]==s[i+1(π[i]+k+11),,i+1]
于是s[0,⋯,π[i]+k−1]==s[i−(π[i]+k−1),⋯,i]s\left[0,\cdots,\pi\left[i\right] + k - 1\right] == s\left[i-\left(\pi\left[i\right] + k -1\right) ,\cdots, i\right]s[0,,π[i]+k1]==s[i(π[i]+k1),,i]
矛盾

如果s[π[i]]==s[i+1]s\left[\pi\left[i\right]\right] == s\left[i+1\right]s[π[i]]==s[i+1],那么π[i+1]=π[i]+1\pi\left[i+1\right] = \pi\left[i\right] +1π[i+1]=π[i]+1

优化2
如果s[π[i]]≠s[i+1]s\left[\pi\left[i\right]\right] \neq s\left[i+1\right]s[π[i]]=s[i+1]
这时候我们需要找到了一个j<π[i]j < \pi\left[i\right]j<π[i]
使得s[0,⋯,j−1]==s[i−j+1,⋯,i]s\left[0,\cdots,j-1\right] == s\left[i-j+1,\cdots,i\right]s[0,,j1]==s[ij+1,,i]

由于s[0,⋯,π[i]−1]==s[i−(π[i]−1),⋯,i]s\left[0,\cdots,\pi\left[i\right]-1\right] == s\left[i-\left(\pi\left[i\right]-1\right) ,\cdots, i\right]s[0,,π[i]1]==s[i(π[i]1),,i]
并且
s[0,⋯,π[π[i]−1]−1]==s[π[i]−1−(π[π[i]−1]−1),⋯,i]s\left[0,\cdots,\pi\left[\pi\left[i\right]-1\right]-1\right] == s\left[\pi\left[i\right]-1-\left(\pi\left[\pi\left[i\right]-1\right]-1\right) ,\cdots, i\right]s[0,,π[π[i]1]1]==s[π[i]1(π[π[i]1]1),,i]
也就是说max⁡j=π[i]−1\max j = \pi\left[i\right]-1maxj=π[i]1

简单来说就是前面那一段和后面那一段是一样的,那么我们意味着前后缀也一样,那么最长的前缀就是前缀函数
在这里插入图片描述
所以我们的过程就是
j(0)=π[i]j^{\left(0\right)} = \pi\left[i\right]j(0)=π[i]
j(i)=π[j(i−1)−1]j^{\left(i\right)}=\pi\left[j^{\left(i-1\right)}-1\right]j(i)=π[j(i1)1]
直到s[j(i)]==s[i+1]s\left[j^{\left(i\right)}\right] == s\left[i+1\right]s[j(i)]==s[i+1]或者j(i)=0j^{\left(i\right)} =0j(i)=0
时间复杂度O(n)O\left(n\right)O(n)

kmp

kmp是一个字符串匹配的算法
主要原理就是,如果某一个位置不相等了,就滑到前缀和后缀相等的地方
那么求前缀和后缀相等,我们可以用前缀函数

为了方便,也可以将前缀函数整体向右移一位
因为是iii这个位置不相等,只能找π[i−1]\pi\left[i-1\right]π[i1]

代码

因为这里用的是数组,所以最好直接存一下字符串长度,不然strlen是O(n)O\left(n\right)O(n)

#include<cstdio>
#include<cstring>const int N = 105;
char pattern[N];//模式串
int len_p;
char text[N];//待匹配串
int len_t;
int nxt[N] = { -1 };//前缀函数void get_next() {int i = 0, j = nxt[0] = -1;while (i < len_p) {if (j == -1 || pattern[i] == pattern[j]) {++i;++j;nxt[i] = j;}else {j = nxt[j];}}
}int kmp() {int i = 0, j = 0;get_next();while (i < len_t && j < len_p) {if (j == -1 || text[i] == pattern[j]) {++j;++i;}else {j = nxt[j];}}if (j == len_p)return i - j;return -1;
}

洛谷3375

如果匹配了,那么要回到π[lenpattern]\pi\left[len_{pattern}\right]π[lenpattern]

#include<cstdio>
#include<cstring>const int N = 1e6 + 5;
char pattern[N];//模式串
int len_p;
char text[N];//待匹配串
int len_t;
int nxt[N];//前缀函数void get_next() {int i = 0, j = nxt[0] = -1;while (i < len_p) {if (j == -1 || pattern[i] == pattern[j]) {++i;++j;nxt[i] = j;}else {j = nxt[j];}}
}void kmp() {int i = 0, j = 0;get_next();while (i < len_t) {if (j == -1 || text[i] == pattern[j]) {++i;++j;if (j == len_p) {printf("%d\n", i - j + 1);j = nxt[j];}}else {j = nxt[j];}}
}int main() {scanf("%s%s", text, pattern);len_p = strlen(pattern);len_t = strlen(text);kmp();for (int i = 1; i <= len_p; ++i) {if (i > 1)printf(" ");printf("%d", nxt[i]);}printf("\n");return 0;
}

字符串周期与border

对于字符串sss0<p≤∣s∣0 < p \le \left|s\right|0<ps,若s[i]==s[i+p]s\left[i\right] == s\left[i + p\right]s[i]==s[i+p]对所有i∈[0,∣s−1∣−p−1]i \in \left[0,\left|s - 1\right| - p - 1\right]i[0,s1p1]成立,则称pppsss的周期

对于字符串sss0≤r<∣s∣0 \le r < \left|s\right|0r<s,若sss长度为rrr的前缀和长度为rrr的后缀相等,则称sss长度为rrr的前缀是sss的border

sss有长度为rrr的border可以推出∣s∣−r\left|s\right| - rsrsss的周期
由前缀函数,可以得到sss所有的的border长度,即π[∣s∣−1],π[π[∣s∣−1]−1],⋯\pi\left[\left|s\right|-1\right],\pi\left[\pi\left[\left|s\right|-1\right]-1\right],\cdotsπ[s1],π[π[s1]1],

codeforce432D

统计每一个border出现的次数

首先求出前缀函数
接着建立一个数组cntcntcntcnt[i]cnt[i]cnt[i]表示s[0,⋯,i]s\left[0,\cdots,i\right]s[0,,i]出现的次数
初始化cnt[i]=1cnt[i] = 1cnt[i]=1
然后从后往前遍历,cnt[π[i]]+=cnt[i]cnt[\pi\left[i\right]] +=cnt[i]cnt[π[i]]+=cnt[i](建议自己模拟一下

#include<cstdio>
#include<cstring>
#include<stack>
using namespace std;const int N = 1e5 + 5;
char s[N];
int len_s;
int nxt[N];int cnt[N];
void get_next() {int i = 0, j = nxt[0] = -1;while (i < len_s) {if (j == -1 || s[i] == s[j]) {++i;++j;nxt[i] = j;}else {j = nxt[j];}}
}int main() {scanf("%s", s);len_s = strlen(s);get_next();fill(cnt, cnt + len_s + 1, 1);for (int i = len_s; i > 0; --i) {cnt[nxt[i]] += cnt[i];}stack<int> st;int t = len_s;while (t > 0) {st.push(t);t = nxt[t];}printf("%d\n", st.size());while (!st.empty()) {int temp = st.top();st.pop();printf("%d %d\n", temp, cnt[temp]);}return 0;
}

字符串压缩

给定一个长度为nnn得字符串sss,我们希望找到最短得字符串ttt,使得s=t∗s = t*s=t
k=n−π[n−1]k = n - \pi\left[n-1\right]k=nπ[n1],如果k∣nk\mid nkn,则t=s[0,1,⋯,k]t=s\left[0,1,\cdots, k\right]t=s[0,1,,k]就是答案
否则不存在有效的压缩

证明:
如果k∣nk\mid nkn,则字符串可以分成长度为kkk的若干块,
由前缀函数,最后一块等于倒数第二块,进而倒数第二块等于倒数第三块,以此类推
最后所有的块都是相等的

最优性证明:暂时没看懂

poj2406

#include<cstdio>
#include<cstring>const int N = 1e6 + 5;
char s[N];
int len_s;
int nxt[N];void get_next() {int i = 0, j = nxt[0] = -1;while (i < len_s) {if (j == -1 || s[i] == s[j]) {++i;++j;nxt[i] = j;}else {j = nxt[j];}}
}int main() {while (scanf("%s", s) && s[0] != '.' && s[1] != '\0') {len_s = strlen(s);get_next();int ans = len_s - nxt[len_s];if (ans == 0)printf("1\n");else if (len_s % ans == 0)printf("%d\n", len_s / ans);else printf("1\n");}return 0;
}
http://www.lryc.cn/news/2872.html

相关文章:

  • 【Python】正则表达式简单教程
  • SAP ABAP Odata
  • Android native ASAN 排查内存泄漏
  • Django项目开发
  • Debezium系列之:深入理解Debezium Server和Debezium Server实际应用案例详解
  • IDE2022源码编译tomcat
  • 214 情人节来袭,电视剧 《点燃我温暖你》李峋同款 Python爱心表白代码,赶紧拿去用吧
  • 数据库范式
  • CUDA中的底层驱动API
  • 【博客616】prometheus staleness对PromQL查询的影响
  • 多传感器融合定位十三-基于图优化的建图方法其二
  • linux 服务器线上问题故障排查
  • Sandman:一款基于NTP协议的红队后门研究工具
  • 【SSL/TLS】准备工作:HTTPS服务器部署:Nginx部署
  • 微搭低代码从入门到精通11-数据模型
  • 【算法基础】前缀和与差分
  • LTD212次升级 | 官网社区支持PC端展示 • 官网新增证件查询应用,支持条形码扫码查询
  • 【安全】nginx反向代理+负载均衡上传webshell
  • 线程池框架
  • 【TCP的拥塞控制】基于窗口的拥塞控制
  • STP协议基础
  • Linux上面配置Apache2支持Https(ssl)具体方案实现
  • [Linux]进程替换
  • 常见的锁策略面试题
  • 设计师一定要知道这几个网站,解决你80%的设计素材。
  • QT基础入门
  • 高数不定积分72题解答
  • 基于北方苍鹰算法优化LSTM(NGO-LSTM)研究(Matlab代码实现)
  • Linux内核启动(理论,0.11版本)分段与分页
  • 数据与C(字符串)