遇到字符串匹配问题,一般我就只能想到O(nm)的朴素算法...
今天有这样一种算法,使得复杂度变为O(n),
这就是KMP(烤馍片)算法
粘一个模板题先:
给出两个字符串(s_1)和(s_2),其中(s_2)为(s_1)的子串,求出(s_2)在(s_1)中所有出现的位置。
然后本题还要求输出所有(s_2)中字符的前缀数组,
现在留下一个疑点,前缀数组(这是啥?),先往后看
首先确定一点,就是在进行字符串匹配时,会是拿一个字符串与另一个字符串的一部分进行比较,那么我们有以下称呼(拿本题进行举例):(s_2)叫做模式串,(s_1)叫做母串
我们先分析朴素算法的原理与弊端:
朴素算法是将每一个母串字符拿出比较,一旦不符合就会放弃前面匹配的所有信息,重头再来,
然而KMP不然,KMP就是一种"失败为成功之母"的算法,每次在匹配失败时利用前面信息进行更高效的匹配,
具体的思想需要用到这个"前缀数组"
那么这是啥呢?
先考虑一个问题:如何利用失败信息,
如果匹配失败,我们会放弃当前匹配,然而这起码证明前面的匹配都是成功的,也就是说,只要我找到模式串前面的能与自己(当前匹配失败之前的一个位置)匹配上的子串,那么一定也能与前面匹配,进行再次查找,而不用进行朴素算法暴力枚举,
前缀数组就是保存这样的指针的数组,预处理大概是这样的:
inline void init(){
p[1]=0;
int j=0;
for(int i=1;i<m;i++){
while(j>0&&b[j+1]!=b[i+1]) j=p[j];
if(b[j+1]==b[i+1]) j++;
p[i+1]=j;
}
}
总体代码是这样的:
#include<cstdio>
#include<cstring>
#include<string>
using namespace std;
char a[1000005];
char b[1000005];
int p[1000005];
int n,m;
inline void init(){
p[1]=0;
int j=0;
for(int i=1;i<m;i++){
while(j>0&&b[j+1]!=b[i+1]) j=p[j];
if(b[j+1]==b[i+1]) j++;
p[i+1]=j;
}
}
inline void find(){
int j=0;
for(int i=0;i<n;i++){
while(j>0&&a[i+1]!=b[j+1]) j=p[j];
if(b[j+1]==a[i+1]) j++;
if(j==m){
printf("%d
",i-m+2);
j=p[j];
}
}
}
int main(){
scanf("%s%s",a+1,b+1);
n=strlen(a+1);m=strlen(b+1);
init();
find();
for(int i=1;i<=m;i++)
printf("%d ",p[i]);
return 0;
}
为什么KMP主体与预处理这么像呢?
因为预处理本身就是
我 匹配 我自己
最后就是输出前缀数组了