今天学习了一下KMP字符串匹配算法。看的是July的博客从头到尾彻底理解KMP(2014年8月22日版),写得很详细。
有两个字符串,母串S,子串P,要在S中找到P。朴素的字符串匹配算法是一一比较两个字符串中的每一个字符,同时记录连续匹配的长度,该长度与P长度一致时说明在S中找到了P。若某个字符失配,则S的索引i和P的索引j都要回退已匹配的长度,然后以下一个字符为首字符继续匹配。时间复杂度为O(m*n),m和n分别为P和S的长度。
而KMP字符串匹配利用了P串中最长共同前后缀的信息,构造出一个next数组,用于在失配时指定P的索引j回退的长度,而S的索引i不需要回退。将时间复杂度降为O(m+n)。
具体代码和注释如下。为了说明next数组构建过程中的关键递归语句k=next[k],我使用了与July不同的用例。
/*2015.8.3cyq*/
#include <iostream>
#include <string>
#include <vector>
using namespace std;
//朴素的字符串匹配,寻找主串S中的子串P,时间复杂度O(m*n)
int strMatch(const string &S,const string &P){
int n=S.size();
int m=P.size();
if(n<m)
return -1;
int i=0,j=0;
while(i<n&&j<m){
if(S[i]==P[j]){//该字符匹配
i++;
j++;
}else{//失配,i回退j步到匹配的首字符,然后加1
i=i-j+1;
j=0;
}
}
if(j==m)//全部匹配,返回P在S中的位置
return i-j;
return -1;
}
//KMP,时间复杂度O(m+n)
//string S="AAACDAAAD ECDAACDAAEB";
//string P= "AACDAAEB";
//next数组: -10100120
//P串AACDAAE与S串AACDAAA不匹配,j从6变为2,比较AAC与S串中间的AAA
int kmpMatch(const string &S,const string &P,const vector<int> &next){
int n=S.size();
int m=P.size();
if(n<m)
return -1;
int i=0,j=0;
while(i<n&&j<m){
if(j==-1||S[i]==P[j]){
i++;
j++;
}else{
j=next[j];//j=0时,若S[i]!=P[0],则j=next[0]=-1,结束递归
}
}
if(j==m)
return i-j;
else
return -1;
}
//获取子串P的next数组
//next[j]=k表示P[j]之前(不包括P[j])有长度为k的相同前后缀
void getNext(const string &P,vector<int> &next){
int len=P.size();
next[0]=-1;
int k=-1;
int j=0;
while(j<len-1){
if(k==-1||P[k]==P[j]){
k++;
j++;
next[j]=k;//该行换成下面4行,可在k缩小后得到同样字符时获得优化
//if(P[j]!=P[k])
// next[j]=k;
//else
// next[j]=next[k];
}else{
k=next[k];//缩短共同前后缀的长度
//该句很关键,以AACDAAEB为例
//next数组为: -10100120
//计算B所对应的next[7]时,因P[2]!=P[6],AAC与AAE不匹配,则k=next[k],k从2变为1
//意义为E前面虽然有长度为2的AA与字符串前缀AA匹配,但E与C不匹配,
//于是只能缩短共同前后缀长度,利用E与C前面的字符串一致的信息,
//结合next[2]=1,即C前面有长度为1的共同前后缀,将E前面的共同前后缀长度缩短到1,
//这样就确定P[1]与P[6]前面是已匹配的,直接比较P[1]和P[6]即可,由于AA与AE又不匹配
//k=next[k],k从1变为0,比较P[0]与P[6],A和E又不匹配,k=next[k],k从0变为-1
//之后k++,j++,next[7]=0,也可见递归的退出条件是匹配或者k==-1
}
}
}
int main(){
string s1="AAACDAAAD ECDAACDAAEB";
string s2= "AACDAAEB";
//next数组为:-10100120
int m=s2.size();
vector<int> next(m);
getNext(s2,next);
for(int i=0;i<m;i++)
cout<<next[i]<<" ";
cout<<endl;
cout<<"strMathch:"<<strMatch(s1,s2)<<endl;//输出13
cout<<"KMP:"<<kmpMatch(s1,s2,next)<<endl;//输出13
return 0;
}