kmp和bf的唯一区别在于主串的i不会回退,并且j也不一定会运动到0的位置
先了解一下bf,暴力
#include<bits/stdc++.h>
using namespace std;
int bf(char *str,char *sub)
{
int lenstr=strlen(str);
int lensub=strlen(sub);
int i=0;
int j=0;
while(i<lenstr&&j<lensub)
{
if(str[i]==sub[j])
{
i++;
j++;
}
else
{
i=i-j+1;
j=0;
}
}
if(j==lensub) return i-j;//在主串中找到了模式串,返回其在主串位置
return -1;//没找到,返回-1
}
int main()
{
char str[100];
char sub[10];
cin>>str>>sub;
cout<<bf(str,sub);
return 0;
}
时间复杂度为O(m*n)
0 1 2 3 4 5 6 7 8 9 10
a b c a b a b c a b c//主串
a b c a b c//模式串
KMP算法的核心是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现就是通过一个next()函数实现,函数本身包含了模式串的局部匹配信息。KMP算法的时间复杂度为O(m+n)
当i=5,j=5时,匹配失败,我们发现i没有必要回退到1,j也没有必要回退到0,因为c前面的ab和最前面的ab是一样的。
0 1 2 3 4 5 6 7 8 9 10
a b c a b a b c a b c//主串
a b c a b c//模式串
——— ———
使用kmp算法时,当i=5,j=5时,i不动,j回退到3,继续匹配。
如何确定j回退的位置呢,靠next数组实现。next[j]=k表示模式串j位置匹配失败,j回退到k位置
next+bf==kmp。
主要难点:如何生成next数组。
如果模式串在j处匹配失败,挑出匹配成功的那部分模式串,然后找到这部分的两个相等的真子串,一个以下标0字符,它在前,一个以j-1下标字符结束,在后。
next[j]=真子串的长度
0 1 2 3 4 5 6 7 8 9 10
a b c a b a b c a b c//主串
a b c a b c//模式串j=5匹配失败
---------
真子串a b c a b c//模式串
——— ———
真子串ab长度为2,next[5]=2
0 1 2 3 4 5 6 7 8 9 10
a b c a b a b c a b c//主串
a b c a b c//模式串
——— ———
给next[0]赋值为-1,next[1]赋值为0;
通过代码实现求next[j]时,是依靠next[j-1]得到的。
设next[j-1]=k,
如果sub[j-1]==sub[k],则next[j]=k+1;
如果不等于,就k=next[k],直到sub[k]=sub[j-1]。如果一直没找到(就是当k==-1时),next[j]=0;
- 第一种情况:sub[0]--sub[k-1]==sub[j-k]--sub[j-1-1],左边加上sub[k],右边加上sub[j-1]。得到 sub[0]--sub[k]==sub[j-k]--sub[j-1]即next[j]=k+1;
- 当sub[j-1]!=sub[k]时,最长前后缀已经不能增加了,next[j]不能等于k+1了,而是要小于k,即next[j]<k,接下来就让sub[0]--sub[k]和sub[j-k]--sub[j-1]这两段自匹配,找到最长前后缀。
感谢比特大博哥的B站视频,让我参悟了kmp的基本思想和模板代码。
再此特别感谢昵称为“sofu6”的博客园主,正是他的博客,使得我理解了k=next[k]。
代码实现
#include<bits/stdc++.h>
using namespace std;
void getnext(int *next,char *sub)
{
int lensub=strlen(sub);
next[0]=-1;
next[1]=0;
int j=2;
int k=0;
while(j<lensub)
{
if(k==-1||sub[j-1]==sub[k])
{
next[j]=k+1;
j++;
k++;
}
else
{
k=next[k];
}
}
}
int kmp(char *str,char *sub)
{
int lenstr=strlen(str);
int lensub=strlen(sub);
int i=0;
int j=0;
int *next=(int*)malloc(sizeof(int)*lensub);
getnext(next,sub);
while(i<lenstr&&j<lensub)
{
if(j==-1||str[i]==sub[j])
{
i++;
j++;
}
else
{
j=next[j];//如果j一直回退到-1,就让主串i++,然后j回到0
}
}
if(j>=lensub) return i-j;//在主串中找到了模式串,返回其在主串位置
return -1;//没找到,返回-1
}
int main()
{
char str[100];
char sub[10];
cin>>str>>sub;
cout<<kmp(str,sub);
return 0;
}
next还可以优化,就是nextval
#include<bits/stdc++.h>
using namespace std;
void getnext(int *next,char *sub)
{
int lensub=strlen(sub);
next[0]=-1;
next[1]=0;
int j=2;
int k=0;
while(j<=lensub)
{
if(k==-1||sub[j-1]==sub[k])
{
if(sub[j]==sub[k+1])
{
next[j]=next[k+1];
}
else
{
next[j]=k+1;
}
j++;
k++;
}
// if(k==-1||sub[j-1]==sub[k])
// {
// next[j]=k+1;
// j++;
// k++;
// }
else
{
k=next[k];
}
}
}
int kmp(char *str,char *sub)
{
int lenstr=strlen(str);
int lensub=strlen(sub);
int i=0;
int j=0;
int *next=(int*)malloc(sizeof(int)*lensub);
getnext(next,sub);
while(i<lenstr&&j<lensub)
{
if(j==-1||str[i]==sub[j])
{
i++;
j++;
}
else
{
j=next[j];//如果j一直回退到-1,就让主串i++,然后j回到0
}
}
if(j>=lensub) return i-j;//在主串中找到了模式串,返回其在主串位置
return -1;//没找到,返回-1
}
int main()
{
char str[1000000];
char sub[1000];
cin>>str>>sub;
cout<<kmp(str,sub);
return 0;
}