一.简单的字符串匹配
我们经常需要在某一串字符中查找是否存在给定的子串,我们将给定的子串叫做模式,查找子串的问题叫做模式匹配。
进行字符串的模式匹配,最简单的方法就是依次将子串与给定的字符串依次进行比较。算法如下:
int patFind(const char* str, const char* pat){
int pos1,pos2;
bool isfind;
int i=0;
pos1=i;
pos2=0;
while(str[pos1]!='\0'&&pat[pos2]!='\0'){
if(str[pos1++]!=pat[pos2++]){
pos1=++i;
pos2=0;
}
}
if(pat[pos2]=='\0'){
return i;
}
return -1;
}
简单的匹配算法存在回溯现象,复杂度为O(m*n)
二.KMP算法的思想
在匹配的时候经常会碰到回溯,而根据对Pattern的先验计算,能避免回溯,提高匹配效率,KMP算法就是通过对Pattern进行先验计算来避免回溯的算法。
如p0…pi与ts…ts+i匹配,pi+1与ts+i+1不等,这时按照简单的匹配算法,将继续第s+1趟比较比较p0…pi-1…pm与ts+1…ts+i…ts+1+m,如果这两者匹配,那么有
p0…pi-1=ts+1…ts+i=p1…pi
这时,如果已知两者不等,那么就不用做第s+1趟比较了。同理,如果已知p0…pi-2不等于p2…pi那么就不用做第s+2趟比较。
依此类推,直到找到最大的k,使得p0…pk=pi-k…pi,这时可以直接做第s+i-k趟比较,并因为已知p0…pk=pi-k…pi=ts+i-k…ts+i了,直接比较pk+1和ts+i+1即可。
这时我们可以定义一个失效函数f(i),比较至pi,使得pi匹配,pi+1不匹配时,其值等于使得p0…pk=pi-k…pi且小于i的最大k值,如果找不到则值为-1.
对于模式pat=abaabcaba,
f(0)为-1
因为p0!=p1, f(1)为-1
因为p0=p2,f(2)为0
因为p0=p3,f(3)为0
因为p0p1=p3p4,f(4)为1
f(5)为-1
f(6)为0
f(7)为1
f(8)为2
如果第s趟比较在第i位失配,则下一趟直接比较pf(i-1)+1和ts+i
三.失效函数的计算
已知f(j-1)=k,则有p0…pk=pj-1-k…pj-1,这时如果pk+1=pj,则f(j)=k+1,如果不等,则继续找f(k),因为p0…pf(k)=pj-1-f(k)…pj-1,这时如果pf(k)+1=pj,则f(j)=f(k)+1,依此类推。我们可以得到下面的方法来计算失效函数。
void failFunc(const char* pat, int* fail){ fail[0]=-1; int len=strlen(pat); int k; for(int j=1;j<len;j++){ k=fail[j-1]; while(k>=0&&pat[k+1]!=pat[j]){ k=fail[k]; } if(pat[k+1]==pat[j]){ fail[j]=k+1; } else{ fail[j]=-1; } cout<<fail[j]<<endl; } }
四.KMP模式匹配算法的实现
int kmpFind(const char* str, const char* pat){ int pos1=0,pos2=0; int patlen=strlen(pat); int* fail=new int[patlen]; failFunc(pat,fail); while(str[pos1]!='\0'&&pat[pos2]!='\0'){ if(str[pos1]==pat[pos2]){ pos1++; pos2++; } else if(pos2==0){ pos1++; } else{ pos2=fail[pos2-1]+1; } } if(pat[pos2]=='\0'){ return pos1-patlen; } return -1; }
虽然计算失效函数需要浪费一定的时间和空间,可是从代码我们可以看到KMP没有任何的回溯,时间复杂度仅为(m+n),当模式比较长时KMP算法比起简单的模式匹配有显著的优势。
运行结果:
int _tmain(int argc, _TCHAR* argv[]) { char str[] = "ababaabaabaabcabaaaabbcccc"; char pat[] = "abaabcaba"; cout<<str<<endl; cout<<pat<<" can be found at position:"<<kmpFind(str,pat)<<endl; return 0; }