<学习笔记>KMP(MP)算法
来源:互联网 发布:windows sdk编程是啥 编辑:程序博客网 时间:2024/05/16 12:09
*(ノ゚▽゚)ノ对方不想和你说话并且向你扔出了一个题。
【题目】 luogu 3375 KMP字符串匹配
题目描述
如题,给出两个字符串s1和s2,其中s2为s1的子串,求出s2在s1中所有出现的位置。
为了减少骗分的情况,接下来还要输出子串的前缀数组next。
(如果你不知道这是什么意思也不要问,去百度搜[kmp算法]学习一下就知道了。)
输入输出格式
输入格式:
第一行为一个字符串,即为s1(仅包含大写字母)
第二行为一个字符串,即为s2(仅包含大写字母)
输出格式:
若干行,每行包含一个整数,表示s2在s1中出现的位置
接下来1行,包括length(s2)个整数,表示前缀数组next[i]的值。
输入输出样例
输入样例#1:
ABABABC
ABA
输出样例#1:
1
3
0 0 1
说明
时空限制:1000ms,128M
数据规模:
设s1长度为N,s2长度为M
对于30%的数据:N<=15,M<=5
对于70%的数据:N<=10000,M<=100
对于100%的数据:N<=1000000,M<=1000
Ps:由于我打的字符串下标是以0为开头的,学的也是以0为开头的,将就看吧。。。
对于这个题,让我们先抛开kmp,想想可以怎么解_(:з」∠)_
首先想到的是暴力匹配。
对于s1和s2,暴力枚举每一个点并进行匹配,由于每次进行匹配时时间复杂度为O(m),至少需要扫(n-m)个点,时间复杂度近似O(nm),TLE!
优化:每次匹配时如果有不同的就直接跳过。
依旧会T…orz
那么…(ノ゚▽゚)ノ kmp…
KMP算法简介
kmp算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt同时发现,因此人们称它为克努特——莫里斯——普拉特操作(简称KMP算法)。KMP算法的关键是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现就是实现一个next()函数,函数本身包含了模式串的局部匹配信息。时间复杂度O(m+n)。(摘自百度百科)
O(m): 对s2的预处理
O(n) : 扫一遍s1,完成匹配
当匹配的过程中发现s1[i]与s2[j]不同时
如 匹配到c和a发现他们不同:
暴力会把aabaabaa往后移一位重新比较。
而kmp算法会因为c之前的已经比较过,我们已经知道s1前面的部分为aabaaba了,显然后移一位不可能匹配成功,后移两位也不可能匹配成功,而后移三位是有可能的。所以kmp算法会直接后移三位后再比对c和s2的第5个字符。
由于s2已经给出,所以我们可以预先处理出当s2[i]匹配失败时转移后应继续判断的位置(s2可能能继续匹配的位置 or s2后移的位数)。 这里用fail数组表示。
如上图中的 fail[7]=4;
可以写出kmp的代码为
void Kmp(){ n=strlen(s1),m=strlen(s2); Done_fail(); for(int i=0,j=0;i<n;++i) // j既可以理解为下标,也可以理解为当前已经匹配了j个字符(0开头的好处2333) { while(j&&s1[i]!=s2[j]) j=fail[j]; // 如果不匹配,就把j往前跳(s2往后移),直到j=0或可以匹配为止 if(s1[i]==s2[j]) ++j; // 可以匹配 j++; if(j==m) Ans[++ans]=i-m+1,j=fail[j]; // 匹配成功,记录下出现的位置并更新j }}
难点:
fail数组的处理:
递推求法:
void Done(){ fail[0]=fail[1]=0; for(int i=1,k=0;i<m;++i) { k=fail[i]; // 已经匹配了i(0~i-1)个字符的fail值,也就是i的fail值,此时我们并不知道s2[i]是否已经/可以匹配,通过判断s2[i],我们用fail[i]来更新fail[i+1]。 while(k&&s2[i]!=s2[k]) k=fail[k];// 如果s2[i]!=s2[k](s2[k]为s2可能能继续匹配的字符),说明s2[i+1]如果失配了,我们并不能直接转移到s2[k+1]尝试匹配。所以我们就像上面的kmp代码一样,不断往前跳,直到找到一个可以匹配的地方为止。(就如同字符串失配了一样) fail[i+1]=(s2[i]==s2[k])?k+1:0; }}
建议自己脑跑一遍以加深理解。
栗子:
网上的博客里还有另一1种理解方法,用next数组表示,统计s2”前缀”和”后缀”的最长的共有元素的长度,next[i]表示s2中前i个字符的”前缀”和”后缀”的最长的共有元素的长度,如 ABCDABBD 对应的next 为 0,0,0,0,1,2,0,0 。所以next[i-1]可以直接用作下标为i的字符失配时应转移到的位置。也就是说fail是next统一往后移了一位。其实思想是一样的。(next应该会更好懂一些)
摘自网上的next代码。
void Done_next(){ next[0] = 0; for (int i = 1,k = 0; i < m; ++i) // k为最大长度 { while(k > 0 && s2[i] != s2[k]) k = next[k-1]; if (s2[i] == s2[k]) { k++; } next[i] = k; }}
那么回到原题,发现只是个模板题,直接上代码好了(:3_ヽ)_
注:原题的字符串是以1为开头的,另外求的也是next数组
#include<iostream>#include<cstdio>#include<cmath>#include<cstring>#include<cstdlib>using namespace std;int n,m,ans;int fail[1010],Ans[1000010];char a[1000010],b[1010];void Done(){ fail[0]=fail[1]=0; for(int i=1,k=0;i<m;++i) { while(k&&b[i]!=b[k]) k=fail[k]; fail[i+1]=b[i]==b[k]?++k:0; }}void Kmp(){ n=strlen(a),m=strlen(b); Done(); for(int i=0,j=0;i<n;++i) { while(j&&a[i]!=b[j]) j=fail[j]; if(a[i]==b[j]) ++j; if(j==m) Ans[++ans]=i-m+1,j=fail[j]; }}int main(){ cin>>a>>b; Kmp(); //cout<<ans<<'\n'; for(int i=1;i<=ans;++i) cout<<Ans[i]+1<<'\n'; // 以一为开头 for(int i=1;i<=m;++i) // 往前移一位 cout<<fail[i]<<' '; cout<<'\n'; return 0;}
ヾノ≧∀≦)o βyё βyё..
The end.
- <学习笔记>KMP(MP)算法
- KMP(MP)算法详解
- kmp算法的弱化版本 mp算法
- KMP算法学习笔记
- 学习笔记-KMP算法
- KMP算法 学习笔记
- Kmp算法学习笔记
- KMP算法学习笔记
- KMP算法学习笔记
- KMP算法学习笔记
- KMP算法学习笔记
- KMP算法学习笔记
- 学习笔记-KMP算法
- KMP算法学习笔记
- KMP算法学习笔记
- KMP算法学习笔记
- KMP算法学习笔记
- KMP算法:学习笔记
- 划分型DP相关
- opencv使图片变亮
- github上的license-许可证
- java 解决 pat 乙级 1072. 开学寄语(20)
- CCF201612-1 中间数
- <学习笔记>KMP(MP)算法
- muduo库源码学习(base)AsyncLogging
- C语言中static变量、函数跟其他变量、函数的区别
- 冒泡排序
- H5新增标签
- Hibernate执行流程&用Hibernate框架完成增删改查的操作
- 哈哈日语 「うそ~」这个词是怎么来的?
- 概率论相关分布总结
- Spring Boot系列七 实现自己的spring boot starter工程