目录
【BF算法】
【KMP算法】
【拓展KMP】
【前言】
著名的模式匹配算法有BF算法和KMP算法,本文章主要着重讲KMP算法及其拓展。
BF(Brute-Force)算法,是最简单直观的模式匹配算法。
看下图,上边的是主串a,下边的是模式串b。分别用i,j做各自串的指针,若a[i]==b[j],i++,j++,继续比较;若不相等,i=i-j+2,j=1,重新开始匹配。
如:第三次匹配的时候a[3]='1',b[3]='3',不相等,则指针后退,i=i-j+2=2,j=1,重新从头开始匹配。
此算法最好情况复杂度为O(n+m),最坏情况复杂度为O(n*m)。算法复杂度较高。
如上图所示,我们发现后退进行的比较有些是不必要的,比如第三趟的时候,我们只需要从i=3,j=1进行比较即可。由此作出改进:每当一趟匹配过程中出现不等时,不需要回溯i指针,而是利用已经得到的部分匹配的结果将模式串向右滑动尽可能远的一段距离后,继续进行比较。这时,我们就需要定义一个数组next[]用来记录模式串T中第j个字符与主串S中相应字符失配时,在模式串中需重新和主串中该字符进行比较的字符的位置,即模式串从T[i]-T[j-1]最大前缀后缀相同的长度+1。
next[] :
当j=1时,next[j]=0;
当1
如:
j 1 2 3 4 5 6 7 8
模式串 a b a a b c a c
next[j] 0 1 1 2 2 3 1 2
【完整代码】
#include
#include
const int maxn=1005;
char S[maxn],T[maxn];
int next[maxn];
int Slen,Tlen;
void get_next()
{
Slen=strlen(S)-1;
Tlen=strlen(T)-1;
int i=1,j=0;
next[1]=0;
while(i<=Tlen)
{
if(j==0||T[i]==T[j])
next[++i]=++j;
else
j=next[j];
}
printf("next[]:\n");
for(i=1;i<=Tlen;i++)
printf("%d ",next[i]);
printf("\n");
}
void KMP()
{
get_next();
int i=1,j=1;
while(i<=Slen&&j<=Tlen)
{
if(j==0||S[i]==T[j])
i++,j++;
else
j=next[j];
}
if(j>Tlen)
printf("匹配成功!\n初始位置为%d\n",i-Tlen);
else
printf("匹配失败!\n");
}
int main()
{
S[0]=1,T[0]=1;
while(~scanf(" %s %s",S+1,T+1))
KMP();
return 0;
}
【再改进】
前面定义的next[]数组仍有缺陷。例如模式串“aaaab”在和主串“aaabaaaab”匹配时,当i=4,j=4时不相等,还需进行i=4,j=3,2,1这三次比较。实际上,因为模式串中第1-3个字符和第四个字符都相等,因此不需要再和主串中第四个字符相比较,而可以直接进行i=5,j=1的比较。也就是说,若按上述定义得到next[j]=k,而模式串中Tj=Tk,则当主串中字符Si!=Sj时,不需要再和Tk进行比较,而直接和Tnext[k]进行比较,换句话说,此时的next[j]应和next[k]相同。
如:
j 1 2 3 4 5 j 1 2 3 4 5 6 7 8
模式串 a a a a b 模式串 a b a a b c a c
next[j] 0 1 2 3 4 next[j] 0 1 1 2 2 3 1 2
nextval[j] 0 0 0 0 4 nextval[j] 0 1 0 2 1 3 0 2
【改进后代码(只改动了自定义函数next())】
#include
#include
const int maxn=1005;
char S[maxn],T[maxn];
int nextval[maxn];
int Slen,Tlen;
void get_nextval()
{
Slen=strlen(S)-1;
Tlen=strlen(T)-1;
int i=1,j=0;
nextval[1]=0;
while(i<=Tlen)
{
if(j==0||T[i]==T[j])
{
if(T[++i]!=T[++j])
nextval[i]=j;
else
nextval[i]=nextval[j];
}
else
j=nextval[j];
}
printf("nextval[]:\n");
for(i=1;i<=Tlen;i++)
printf("%d ",nextval[i]);
printf("\n");
}
void KMP()
{
get_nextval();
int i=1,j=1;
while(i<=Slen&&j<=Tlen)
{
if(j==0||S[i]==T[j])
i++,j++;
else
j=nextval[j];
}
if(j>Tlen)
printf("匹配成功!\n初始位置为%d\n",i-Tlen);
else
printf("匹配失败!\n");
}
int main()
{
S[0]=1,T[0]=1;
while(~scanf(" %s %s",S+1,T+1))
KMP();
return 0;
}
问题定义:给定两个字符串 S 和 T(长度分别为 n 和 m),下标从 0 开始,定义extend[i]等于S[i]...S[n-1]与 T 的最长相同前缀的长度,求出所有的extend[i]。
举个栗子:
为什么说这是 KMP 算法的扩展呢?显然,如果在 S 的某个位置 i 有extend[i] 等于 m,则可知在 S 中找到了匹配串 T,并且匹配的首位置是 i。而且,扩展 KMP 算法可以找到 S 中所有 T 的匹配。
怎么实现呢?
推一篇博客:扩展 KMP 算法
【代码】
#include
using namespace std;
const int maxn=1e5+10;
char S[maxn],T[maxn];
int net[maxn],extend[maxn];
int n,m;
void Getnet()
{
int a=0,p=0;
net[0]=m;
for(int i=1;i=p||i+net[i-a]>=p){
if(i>=p) p=i;
while(p=p||i+net[i-a]>=p){
if(i>=p) p=i;
while(p