关于理解计算失配函数的一点小心得。
首先,感谢Jake Boxer的文章给我的帮助。
在jake的文章里面,说的,前缀和后缀是理解的关键。请先阅读以下jake的文章(不然可能不好理解)。
正题:假设字符串 P 的长度是 m。我们给定 P 的失配函数为 failure[m],failure[0] = 0。
假设 0<= j < m ,如果我们知道了由 failure[j-1] 推导出 failure[j] 的公式的话,我们就很容易求出失配函数了。
关于 failure[j-1] 与failure[j]的关系,就要考虑到前缀和后缀的问题了。jake的文章中提到的,其实失配函数里面存储是与当前子字符串的后缀相匹配的当前的子字符串的最大前缀的长度。jake的原文是:The length of the longest proper prefix in the (sub)pattern that matches a proper suffix in the same (sub)pattern.
比如 字符串 P = “abababca” ,在 j = 1的时候,子字符串是 “ab” ,j = 2 的时候 子字符串是 “ aba” , j = 3 的时候子字符串是 “abab”。
那么我们可以看出 failure[1] = 0 ,failure[2] = 1, failure[3] = 2 。
现在考虑 failure[j-1] 已知,表示我们知道 j-1 长度的子字符串的最大前缀(其实,是与后缀相匹配的最大前缀,后面都省略为最大前缀),那么 j 长度的子字符串的最大前缀。
我们可以这样来考虑, j-1 的最大前缀 后面的一个字符是不是与 j的字符相同,如果相同,那好,这就是j 的最大前缀了。如果不同,我们就缩短最大前缀,再匹配,这里要通过 i = failure[ failure[j-1] ] 的方式来获取更小的最大前缀,然后再次比较 i+1 处的字符是不是与 j相同。重复此操作。
failure[j-1] 与 failure[j] 的关系也就是:
a. 当j=0是,failure[j] = 0 ,
b. 当P(j) = P(failure[j-1]+1)时,failure[j] = failure[j-1]+1,
c.当j> 0 且 P(j) != P(failure[j-1]+1)时,迭代查找 failure[failure[j-1]] +1 位置的字符与 P(j) 是否相同,直至出现a或者b步的情况出现。
下面是我自己实现的代码:
void fail(char* pattern){
int m = strlen(pattern);
cout<<"m=="<<m<<endl;
int* failure = new int[m];
int i = 0;
failure[0] = 0;
for(int j=1;j<m;j++){
i = failure[j-1];
while(i>0 && (pattern[i] != pattern[j])){
i = failure[i-1];
}
cout<<"i=="<<i<<"--j="<<j<<endl;
cout<<"pat[i]=="<<pattern[i]<<"--pat[j]=="<<pattern[j]<<endl;
cout<<"---------------"<<endl;
if(pattern[i]==pattern[j]){
failure[j] = i+1;
}
else{
failure[j] = 0;
}
}
for(int k=0;k<m;k++){
cout<<failure[k]<<" ";
}
}
补上KMP的匹配算法
int pmatch(char* string,char* pat){
int n = strlen(string);
int m = strlen(pat);
int* failure = fail(pat);
int i = 0;
int j=0;
while(i<n && j<m){
if(string[i]==pat[j]){
i++;
j++;
}
else{
while(j>0 && string[i] != pat[failure[j-1]+1]){
j = failure[j-1];
}
if(string[i] == pat[j]){
j++;
}
i++;
}
}
if(j==m){
return i-j;
}
return -1;
}