BF算法思路直观简明,担当匹配失败时,主串的指针i 总数回溯到i-j+2位置,模式串的指针总是恢复到首字符位置j = 1;因此,算法时间复杂度高,下面介绍另一种改进的模式匹配算法。
KMP算法可以在O(n + m)的时间数量级上完成串的模式匹配。
下面是匹配过程。
从上图可得,匹配仅需要从模式中第k个字符与主串中的第 i 个字符开始,依次向后进行比较。若令next[j] = k,则next[j]表明当模式中第j个字符与主串中相应字符"失配"时,在模式中需要重新和主串中该字符进行比较的字符位置,由此可引出模式串的next函数的定义:
模式串的next[函数]:
匹配过程中,如果i = j,各增1,否则i不变,j退回到next[j]位置再比较,重复上述步骤i,以此类推,直至下列两种可能:一种是j退到某个next值时字符比较相等,则指针各自增1,继续进行匹配;另一种是j退到值为零,则此时需要将模式继续向右滑动一个位置,即从主串的下一个字符起和模式重新匹配。
KMP算法形式上和BF算法极为相似,不同之处仅在于:当匹配过程中产生"失配"时,指针i不变,指针j退回到next[j]所指示位置上重新进行比较,并且当指针j退至0时,指针i和指针j需要同时增1,即若主串的第i个字符和模式的第1个字符不等,应从主串的第i+1个字符起重新匹配
KMP算法:
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; //匹配失败
}
要实现KMP算法,我们还需要计算next函数值,令next[1] = 0:
.
void get_next(SString T, int next[])
{//求模式串T的next函数值并存入数组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];
}
}
- 算法时间复杂度为O(m),通常,模式串的长度m比主串的长度n要小的多,因此,对整个匹配算法来说,所增加的这点时间是值得的。
- 但前面定义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值更改为0,当前next值作为nextval[j]的值
计算next函数修正值
void get_nextval(SString T, int nextval[])
{
//求模式串T的next函数修正值并存入数组nextval
int i = 1,j = 0;nextval[1] = 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 h = nextval[j];
}
}
未修正代码实现:
#include<iostream>
#include<string>
using namespace std;
int next[105];
int Index_KMP(string S,string T,int pos)
{
int i = pos;
int j = 1;
while(i <= S.size()&& j <= T.size()) //两串均未比较到串尾
{
if(j == 0 || S[i] == T[j])//继续比较后续字符
{
i++;
j++;
}
else
j = next[j]; //模式串向右移动
}
if(j > T.size())
return i-T.size(); //匹配成功
else
return 0; //匹配失败
}
void get_next(string T, int next[])
{//求模式串T的next函数值并存入数组next
int i = 1;
next[1] = 0;
int j = 0;
while(i < T.size())
{
if(j == 0 || T[i] == T[j])
{
i++,j++,next[i] = j;
}
else
j = next[j];
}
}
int main()
{
string S, T;
cout<<"输入S:";
cin>>S;
cout<<"输入T:";
cin>>T;
get_next(T,next);
cout<<Index_KMP(S,T,1);
return 0;
}
修正代码实现:
#include<iostream>
#include<string>
using namespace std;
int next[105];
int Index_KMP(string S,string T,int pos)
{
int i = pos;
int j = 1;
while(i <= S.size()&& j <= T.size()) //两串均未比较到串尾
{
if(j == 0 || S[i] == T[j])//继续比较后续字符
{
i++;
j++;
}
else
j = next[j]; //模式串向右移动
}
if(j > T.size())
return i-T.size(); //匹配成功
else
return 0; //匹配失败
}
void get_nextval(string T, int nextval[])
{
//求模式串T的next函数修正值并存入数组nextval
int i = 1,j = 0;nextval[1] = 0;
while(i < T.size())
{
if(j == 0 || T[i] == T[j])
{
i++;
j++;
if(T[i] != T[j])
nextval[i] = j;
else
nextval[i] = nextval[j];
}
else j = nextval[j];
}
}
int main()
{
string S, T;
cout<<"输入S:";
cin>>S;
cout<<"输入T:";
cin>>T;
get_nextval(T,next);
cout<<Index_KMP(S,T,1);
return 0;
}
内容参考:《数据结构》 严蔚敏