KMP算法是一种改进的字符串匹配算法,KMP算法的核心是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现就是通过一个next()函数实现,函数本身包含了模式串的局部匹配信息。KMP算法的时间复杂度
O(m+n)
。
KMP算法的匹配过程:
KMP算法是怎样减少模式串与主串的匹配次数呢???
每当匹配过程出现字符比较不等时
- 主串的
i 指针不动
- 模式串的
j
指针定位
到某个数
上面提到的模式串中 j 指针定位到某个数其实就是next值
若令 next[j]=k,则next[j] 表明当模式中第 j 个字符与主串中相应字符“失配”时,在模式中需要重新和主串中该字符进行比较的字符的位置。
我们规定任何一个串,next[1]=0,举个例子求next:
已知模式串为ABABABB(如下图),在 j =5时,模式串与主串对应字符不匹配,那么求next值就需要找 j 前面的 j-1(即为4) 个字符。j 要移动的下一个位置需要满足这样的性质:最前面的k(即为2)个字符和 j 之前的最后k(即为2)个字符是一样的
,且 k 必须是最大
的情况。
此时FL=“AB”=FR,想要知道下一次的 j 指针对应的值只需要将 FL 与 FR 相重合(通过移动重合)即可 ,重合后此时 j 指向的值为第三个字符(A),说明next值为3。
用公式验证一下,next=FL串长+1=FR串长+1=3
通过上面的例子再来理解一下模式串next函数的定义:
next[j] = 0的意思是主串的第 i 个字符与模式的第一个字符不等,应从主串的第 i+1 个字符起重新进行匹配
由此上面的定义我们可以推出模式串的next函数值,举个例子:
如果按照上面的next值进行匹配,就是下面的匹配过程:
KSP算法如下:
int Index_KMP(SString S,SString T,int pos)
{
i=pos;j=1;
while(i<=S.length && j<=T.length) //两个串均未比较到串尾
{
if(j==0 || S.ch[i]==T.ch[j])
{++i;++j;} //继续比较后续字符
else
j=next[j]; //模式串向右移动
}
if(j>T.length) return i-T.length; //匹配成功
else return 0; //匹配失败
}
计算next函数值(
O(m)
)算法如下:
void get_next(SString T,int next[])
{
i=1;next[1]=0;j=0;
while(i<T.length)
{
if(j==0 || T.ch[i]==T.ch[j])
{++i;++j;next[i]=j;}
else
j=next[j];
}
}
我们前面定义的next函数在某些情况下尚有缺陷。
例如模式”aaaab“在和主串”aaabaaaab“匹配时,当 i =4、j =4 时S.ch[4] ≠ T.ch[4],由 next[j]的指示还需要进行i=4、j=3,i=4、j=2,i=4、j=1这3次比较。实际上,因为模式中1~3个字符和第4个字符都相等,因此不需要再和主串的第4个字符相比,可以将模式连续向右滑动4个字符的位置直接进行i=5、j=1的字符比较。直接将next[3]、next[2]、next[1]改为0即可。nextval[j] 即为修正后的next值
计算next函数修正值算法如下:
void get_nextval(SString T,int nextval[])
{
i=1;nextval[1]=0;j=0;
while(i<T.length)
{
if(j==0 || T.ch[i]==T.ch[j])
{
++i;++j;
if(T.ch[i]!=T.ch[j])
nextval[i]=j;
else
nextval[i]=nextval[j];
}
else j=nextval[j];
}
}
整体代码:
#include <stdio.h>
#include <string.h>
int lens,lent; //子串的长度
int next[100];
int KMP(char s[],char t[],int pos)
{
int i=pos;int j=1;
while(i<=lens && j<=lent)
{
if(j==0||s[i]==t[j])
{
++i;
++j;
}
else
j=next[j];
}
if(j>lent) return i-lent;
else return 0;
}
void getnext(char t[],int next[])
{
int i=1,j=0;
next[1]=0; //第一个位0
while (i<lent)
{
if (j==0 || t[j]==t[i]) //当j为第一个 或者 前一个字母和后一个字母相等
{
++i;
++j;
next[i]=j;
}
else
j=next[j];
}
}
int main()
{
char s[100],t[100];
int i;
gets(s);
gets(t);
lens=strlen(s);
lent=strlen(t);
for (i=lens;i>0;i--) //将主串所有元素全部后移
{
s[i]=s[i-1];
}
for(i=lent;i>0;i--)
{
t[i]=t[i-1]; //将子串所有元素全部后移
}
getnext(t,next);
int x=KMP(s,t,1);
printf("%d",x);
return 0;
}
next修正后的代码:
#include<iostream>
#include<cstring>
using namespace std;
int lens,lent;
int next[100];
int KMP(char s[],char t[],int pos)
{
int i=pos;int j=1;
while(i<=lens && j<=lent)
{
if(j==0||s[i]==t[j])
{
i++;j++;
}
else
j=next[j];
}
if(j>lent) return i-lent;
else return 0;
}
void getnext(char t[],int next[]) //这个next是修正值哦
{
int i=1;int j=0;
next[1]=0;
while(i<lent)
{
if(j==0||t[j]==t[i])
{
i++;j++;
if(t[j]!=t[i])
next[i]=j;
else
next[i]=next[j];
}
else
j=next[j];
}
}
int main()
{
char s[100],t[100];
int i;
gets(s);
gets(t);
lens=strlen(s);
lent=strlen(t);
for(i=lens;i>0;i--)
{
s[i]=s[i-1];
}
for(i=lent;i>0;i--)
{
t[i]=t[i-1];
}
getnext(t,next); //只需要用模式串就可以求出next
int x=KMP(s,t,1);
cout<<x<<endl;
for(int i=1;i<=lent;i++)
cout<<next[i]<<" ";
return 0;
}
求next也可以这样写:
求nextval也可以这样写:
推荐小张同学的博客,非常详细https://blog.csdn.net/l218623/article/details/104193791
另外可以看下天勤的这节视频
这个是关于BF算法的讲解BF算法—串的模式匹配算法
借鉴:《数据结构》 严蔚敏