在一个字符串中找另一个字符串是否包含其子串的算法有很多实现方式,比如常见的bf,bm,sunday算法等等。相比其它几种算法,kmp真的可以称得上是一种恶心的算法,因为对于速度来说,它没有bm,sunday等算法块,但是理解上来说却比后面的理解难很多。由于其特殊的性质,bf和kmp仍然是在字符串匹配算法中占据主要地位的,所以就来简单的看下kmp算法的具体实现。
所谓bf就是一种暴力求解的方式,这种方式在程序设计上很常见。我们列举程序的所有可能性,然后再一一做比较,看是否能够得到原问题的解。但是这种只是在程序数据量比较小的时候能够采用的方式,如果数据量稍微一大,那么这种方式必然被pass。我们在对字符串做搜索功能的时候也一样,如果原字符串很大,而被搜索的字符串长度也比较大的时候,显示bf就无能为力了。
bf实现的算法形式如下:
# include <stdio.h>
int main(void){
int i = 0,j=0;
char s1[] = "dfgfmgdfg";
char s2[] = "fmg";
while(s1[i+j] != '\0' && s2[i] != '\0'){
if(s1[i+j] == s2[i]){
i++;
}else{
j++;
i = 0;
}
if(s2[i] == '\0'){
printf("%d ",j);
j++;
i = 0;
}
}
return 0;
}
这里定义了一个记录位置的变量j,我们每次穷举字符s1的每个字符与s2比较,如果相等就让i加1,如果不相等的话,就让位置变量+1,并且匹配重新重零开始。可以看到这样的算法其实很多时候在做无用功(本例看不出来),如果在最坏的情况下,其算法复杂度可能到达o(n*m)。
下面就该kmp上场了,kmp对于bf的改进之处就在于他的移动不是简单的每次向右移动,而是按照前缀函数来移动。
kmp的算法实现方式如下:
# include <stdio.h>
# include <string.h>
void getNext(char *str,int * next); //构造前缀函数
int main(void){
int next[100],i,j;
char str1[] = "fabcabg";
char str2[] = "abcab";
int len1 = strlen(str1);
int len2 = strlen(str2);
i =0;j=0;
getNext(str2,next);
while(i<len1){
if(j == -1 || str1[i]==str2[j]){
i++;
j++;
}else{
j = next[j]; //移动的次数
}
if(j == len2){
printf("成功,从第%d行开始匹配",i-len2);
}
}
return 0;
}
void getNext(char *str,int * next){
//i代表已经匹配的真后前缀字符串长度
//j代表已经匹配的字符数
int i=0,j;
int len = strlen(str);
next[0] = -1;
next[1] = 0;
//构造前缀的主要核心代码
for(j=2;j<len+1;j++){
while(i>0 && (str[i] != str[j-1])){
i = next[i];
}
if(str[i] == str[j-1]){
i++;
}
next[j] = i;
}
}
kmp具体的算法分析过程,可能是一个相当不好理解的过程,建议多看些其它具体分析的文章,也可以看下算法导论上对这部分的介绍