kmp算法虽然是一个非常老的算法了,各界OI比赛最近也很少碰过。但是,作为一个很经典的算法,且在2016GDOI中专门出了kmp模板题(当时还是个蒟蒻,暴力只拿了30分……)。所以,今天又打了一遍kmp且写了题解,既是帮大家理解kmp,也是对我自己知识的一个梳理。
首先,我们要了解相较于普通字符串匹配快在哪。普通匹配是一位一位往前推,再一次次从选定的开头往后对比,时间复杂度近似于O(N^2)。而kmp快就快在它不用一位一位的选定开头,而是动态以最优的开头作比较,比朴素算法效率提高了不少。看不懂这段话的人也不用着急,往后看就对了。
首先,让我们来谈谈此算法的思路。针对于每一位,kmp算法已经预处理出了一个对应kmp数组的单元,映射着如果此位失配,它可能的最靠后的一个重新开头是哪一个。让我们举一个例子:假如让aaab与aab匹配。一开始,aab的aa与aaab的开始的aa成功匹配,但到了第三位失配了。此时,朴素算法会跳出,找到下一个开头进行比对。然而kmp算法用kmp数组得知,这位失配后应该可能却可以与第二位匹配成功,而又成功,于是又继续往后匹配,然后就匹配成功了,只比较了5次,比O(n^2)好了不少。
而kmp函数又是怎么算出来的呢?其实,只要自己与自己匹配即可。
kmp[i]的基本定义是:在第1-第i-1位中前缀与后缀相同的部分最长是多长。这样,即可以理解为,若第i位失配了,则至少要往前跳多少步,才可能重新匹配得上。这样便可以解释前面的例子了。
KMP算法的核心思想是next数组。
接下来,我来谈谈我对KMP数组的理解。
KMP算法是用来匹配有多少相同字串的一种算法。
1、next数组记录前缀与后缀相等的位置,然后跳到这。
2、数组即记录后缀与前缀相等的个数
如ABCABC
那么next数组就是
/*
hiho 1015
题意:s1在s2中出现了多少次
*/
#include<cstdio>
#include<cstring>
using namespace std;
const int N1=1e4+50;
const int N2=1e6+10;
char s1[N1];
char s2[N2];
int next[N1];
int main()
{
int n;
scanf("%d",&n);
while(n--){
scanf("%s%s",s1,s2);
int i=0;
int j=-1;
next[0]=-1;
int len=strlen(s1);
while(i<len){
if(j==-1||s1[i]==s1[j]) next[++i]=++j;
else j=next[j];
}
i=j=0;
int sum=len;
len=strlen(s2);
int ans=0;
while(i<len){
if(j==-1||s1[j]==s2[i]){
i++;
j++;
}
else j=next[j];
if(j==sum) ans++;
}
printf("%d\n",ans);
}
return 0;
}
/*
洛谷 p3375
题意 s1是子串
输出:
若干行,每行包含一个整数,表示s2在s1中出现的位置
接下来1行,包括length(s2)个整数,表示前缀数组next[i]的值。
*/
#include<bits/stdc++.h>
using namespace std;
const int N1=1e6+50;
char s1[N1];
char s2[N1];
int _next[N1];
int main()
{
scanf("%s%s",s2,s1);
int i=0;
int j=-1;
_next[0]=-1;
int len1=strlen(s1);
while(i<len1){
if(j==-1||s1[i]==s1[j]) _next[++i]=++j;
else j=_next[j];
}
i=j=0;
int sum=len1;
int len=strlen(s2);
//int ans=0;
while(i<len){
if(j==-1||s1[j]==s2[i]){
i++;
j++;
}
else j=_next[j];
if(j==sum){
//ans++; 这个可以求s1在s2中有多少个
printf("%d\n",i-len1+1);
}
}
for(int i=1;i<=strlen(s1);i++) printf("%d ",_next[i]);
printf("\n");
return 0;
}