KMP算法是两个字符串进行匹配时使用的算法,在原先的暴力匹配法基础上进行了加速,使得时间复杂度降到了O(N)而不再是O(MN),提升了效率。这里只介绍KMP算法的使用方法,不介绍其中的原理和证明过程。
next数组
next数组是是kmp算法得以加速的关键,它表示的是字符串中的某个字符之前的子串,前缀和后缀相同的最大长度。这里注意对于字符串中的每一个字符,next数组都要有对应的值。
求出next数组的方法,如果前后缀相同,则next数组对应位置的值在前一位的基础上加1,如果不同,则需要回退。
vector<int> get_next(string str2)
{
int n2=str2.length();
if(n2==1)
{
return {-1};
}
vector<int> next(n2,0);
next[0]=-1;next[1]=0;//人为规定
int i=2,k=0;//i用于遍历,k用于比对
while(i<n2)
{
if(str2[i-1]==str2[k])//前后缀相同
{
++k;
next[i]=k;
++i;
}else if(k>0)//前后缀不同,且k值可回退
{
k=next[k-1];
}else//前后缀不同,且k值为0,不可回退
{
++i;//next数组已都初始化为0,这里不用修改
}
}
return next;
}
匹配过程
这里先说一下暴力法,暴力法与KMP算法有一点相似,都是会将两个字符串中的字符一一比对。不同之处在于,对于暴力法,如果匹配失败,原字符串对应的指针回退到一开始匹配的后一位,而子串则需要从头重新匹配。而KMP算法的加速之处在于原字符串指针不动,子串指针也不需要立即回退到开头。这一过程也可以换个角度理解,暴力法是在匹配失败后,将子串一个字符一个字符地往后推,而KMP算法可以一次性将子串向后推多个字符。
int kmp(string str1,string str2)
{
int n1=str1.length();//str1是原字符串
int n2=str2.length();//str2是需进行匹配的子串
if(n1<n2)
{
return -1;
}
vector<int> next=get_next(str2);
int i1=0,i2=0;//遍历两个字符串的指针
while(i1<n1&&i2<n2)
{
if(str1[i1]==str2[i2])//两字符串对应字符匹配成功
{
++i1;
++i2;
}else if(i2>0)//匹配失败,i2回退
{
i2=next[i2];
}else//匹配失败,且i2不可回退
{
++i1;
}
}
return i2==n2?i1-i2:-1;//i2==n2即为匹配成功,返回str1对应首字符的下标
}
对数器
这里还是再贴出对数器的代码,随机生成字符串进行对比(这里的随机生成方法较为粗糙,可以改进)。可以将暴力法和KMP算法生成的结果进行对比。
int compare()
{
int len1=rand()&63;
int len2=rand()&31;
for(int times=1;times<=TIME_N;++times)
{
string str1(len1,0),str2(len2,0);
for(int i=0;i<len1;++i)
{
str1[i]=1+rand()%126;
}
for(int i=0;i<len2;++i)
{
str2[i]=1+rand()%126;
}
int ret1=kmp1(str1,str2);
int ret2=kmp2(str1,str2);
if(ret1!=ret2)
{
return times;
}
}
return 0;
}
这里字符串匹配函数的返回值,匹配失败返回-1,成功返回匹配成功时子串的第一个字符在原字符串中对应位置的下标。