BF算法匹配的话,确实是最容易想到和实现的,也难怪现在还是广泛应用中,一般情况下,BF算法的效率还是可以的,但是最坏情况下就不行了,KMP算法就解决了这个问题,相应地,当然比BF算法复杂了,一开始看KMP 真的不知道它在说啥,看了几遍才理清思路。
KMP的主要思想(有一些自己的理解):匹配模式T和主串T,在已经匹配了j-1个的情况下,如果现在S[i]和T[j-1]不相等,那么如果是BF算法,就从T[0]重新开始,而且i坐标也要回去,这样就浪费了时间,KMP算法则是从已经匹配的j-1中选择一个位置k,从位置k开始,进行S[i]和T[k]的比较,找出这个k就是KMP的高明和困难之处。
假设现在S[i-1]应该与T中的第k(k<j)个字符继续比较,则模式T中第k个字符之前的k-1个字符必定与主串S中第i个字符之前的k-1个字符相等。
即有 (T[0],T[1],.......,T[k-2])= (S[i-k],S[i-k+1],......,S[i-2])
而已经得到模式T和主串S“部分匹配”的结果是
(T[0],T[1],.....,T[J-K],T[J-K+1],.....T[J-2])=(S[i-j],S[i-j+1],.....,S[i-k],S[i-k+1],.......,S[i-2])
综合上面两个式子,可得(T[0],T[1],......,T[K-2])=(T[J-K],T[J-K+1],.......,T[J-2]);
所以只要找出满足上式的k就可以让,S的第i个字符与模式T的第k个字符开始比较,也就省下了时间。
求K的过程,KMP是用一个next[]的整形数组,这个数组的定义是这样的:
1、当j=0时,next[j]=-1;
2、max{k|0<kj-1 且 T[0],T[1],......,T[K-2])=(T[J-K],T[J-K+1],.......,T[J-2]);}
3、0 其他情况
求解next[j]的方法:
设模式T为"abaabcac"
序号 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
T[j] | a | b | a | a | b | c | a | c |
k | 0 | 0 | 1 | 1 | 2 | 0 | 1 | |
T[k]与T[j]比较 | != | == | != | == | != | == | != | |
next[j] | -1 | 0 | 0 | 1 | 1 | 2 | 0 | 1 |
假设现在next[0]~next[4]都已经球出来了,现在要求next[5],求解过程是这样的:
令k=next[4]=1,比较T[4]与T[1],T[4]==T[1],所以next[5]=next[4]+1=k+1=2;
如果要求next[6],那么就令k=next[5]=2,T[5]!=T[2],令k=next[2]=0,T[5]!=T[0],令k=next[0]=-1,此时,当k=-1时,就默认是寻找k的过程结束了,此时就和上面一样,next[6]=next[5]+1=k+1=0;
由上面两个例子可以知道,求解next[j]就是先令k等于next[j-1],然后就是比较T[j-1]与T[k],不想等时,就令k=next[k],如此反复做下去,直到T[j-1]==T[k]或者是k=-1为止,
此时next[j]=k+1;
下面是KMP 和 BF 的代码
文件"mystring.h"
#include<string>
using namespace std;
//BF算法
int Index_BF(string S,string T,int pos)
{
//中要满足1<=pos<=S.getlength()-T.getlength()+1
int i=pos-1,j=0;
while(S[i+j]!='\0' && T[j]!='\0')
{
if(S[i+j]==T[j])
j++;
else
{
i++;
j=0;
}
}
if(T[j]=='\0')
return i;
else
return -1;
}
//下面是KMP算法
void Get_next(string T,int next[])
{
//求模式T的next函数值并存入数组next中,为后面的模式匹配做准备
int k=-1,j=0;
next[0]=-1;
int length=T.length();
while(j<length)
{
if( (k==-1)||(T[j]==T[k]) )
{
++j;
++k;
next[j]=k;
}
else
k=next[k];
}
}
int Index_KMP(string S,string T,int pos,int next[])
{
//利用模式T的next函数求模式T在主串S的第pos个字符后面的位置
//其中要满足1<=pos<=S.getlength()-T.getlength()+1
int i=pos-1,j=0;
int n=S.length();
int m=T.length();
while(i<n && j<m)
{
if(j==-1 || S[i]==T[j])//j=-1时,是在T的第一个元素就和S[i]不相等时
{
++i;
++j;
}
else
j=next[j];
}
if(j>=m)
return i-m;//匹配成功
else
return -1;//匹配失败
}
主函数测试"main.cpp"文件
#include"mystring.h"
#include<iostream>
using namespace std;
int main()
{
string S("wangjikaiailiudan");
string T("liudan");
int next[1024];
memset(next,0,sizeof(next));
Get_next(T,next);
int pos1=Index_KMP(S,T,7,next);
int pos2=Index_BF(S,T,8);
if(pos1==-1 && pos2==-1)
cout<<"无法匹配"<<endl;
else
cout<<"模式T在主窜S的第 "<<(pos1+pos2)/2<<" 号位置开始匹配成功"<<endl;
return 0;
}