KMP(烤馍片)算法
啥是烤馍片
烤馍片就是好吃的
KMP就是对于暴力求一个字符串是否是另一个字符串的子串的优化
算法思想 & 例题
- 给出两个字符串S1,S2,要求判断S2是否为S1子串
- 首先先来想想暴力的思想,很容易我们就会想到暴力枚举S1中每一个点作为起点,如果向后延伸S2长度中所有点都与S2相同,则证明含有,否则枚举下个点
- 但是这是nm的效率 显然对于一些大数据我们是过不去的
- 所以烤馍片就此诞生,它可以做到线性的效率
也许你们会更快,但我哈希永远不慢- 首先我们进行预处理 预处理出一个fail数组 fail数组记录啥呢? fail[i]表示在a[1..i]中的最长公共前后缀(不能为自己)
举个栗子:
a: a b a b a b a a c
fail: - 假定你已经知道fail数组咋求,那么fail数组有什么用呢?
对于这样一个序列 a b a b a b a a c
问是否存在a b a b a a c这样一个子串
显然它是存在的 如果我们要暴力求解的话,先枚举第一个a 然后枚举到第六个位置的时候发现不匹配 然后重新来
这样一个过程显然是很浪费时间的 因为我们已经知道从第一个到第四个其实是匹配的 那我们可以直接在不匹配的时候将下面这个匹配串开头放到第三个字符a的位置
那么这个放到哪个位置怎么去确定呢?
没错就是我们的fail 如果当前位置失配了 没必要从头再枚举一遍 而是直接从失配那个地方开始枚举 - 所以fail到底怎么求呢?
假设已经求出了fail[1…6],正在求fail[7]。
发现 A[fail[6]+1] = A[7],那么fail[7]=fail[6]+1,且一定是最优的。
接下来求fail[8],发现A[fail[7]+1]!=A[8],说明接下来我们要考虑次优解(即判断以7为结尾的次长公共前后缀能否向后继续扩展)
由fail数组的定义得出,以7为结尾的次长公共前后缀长度为fail[fail[7]],如果再次失配(即A[fail[fail[7]]+1]!=A[8]),则需要继续跳fail,直到匹配上(出现了A[j+1] = A[8],此时fail[8] = fail[j]+1)或者fail已经为0且仍旧失配。
例如上面A串最终有A[8] == A[0+1],所以fail[8]=fail[0]+1=1。
一共只有上面两种情况,代码实现是比较简单的。 - 所以看代码实现吧
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e7;
char s1[maxn],s2[maxn];
int fail[maxn];
int main(){
scanf("%s%s",s1+1,s2+1);
int len1 = strlen(s1 + 1);
int len2 = strlen(s2 + 1);
for(int i = 2,j = 0;i <= len2;++i){
while(j && s2[i] != s2[j + 1])j = fail[j];
if(s2[i] == s2[j+1])fail[i] = ++j;
}
for(int i = 1,j = 0;i <= len1;++i){
while(j && s1[i] != s2[j + 1])j = fail[j];
if(s1[i] == s2[j + 1])j++;
if(j == len2){printf("Yes\n");return 0;}
}
printf("No\n");
return 0;
}
如初见 与初见