浅谈字符串匹配算法—BF算法及KMP算法

出处:http://blog.csdn.net/jiajiayouba/article/details/9178789

字符串匹配,在实际编程中经常遇到。其相应的算法有很多,本文就BF算法和KMP算法,谈一下自己的理解。并结合平时编程,修改了一下,使其更符合我们的使用习惯。(注:标准BF算法和KMP算法,为研究方便,其字符数组[0]存放的都是字符串的长度。本文讲解中,并没有保存字符串长度。后面给出的示例代码中,字符数组中是否保存有字符串长度,都给出了相应的算法代码。)

一、BF算法(Brute Force):

BF算法核心思想是:首先S[1]和T[1]比较,若相等,则再比较S[2]和T[2],一直到T[M]为止;若S[1]和T[1]不等,则T向右移动一个字符的位置,再依次进行比较。如果存在k,1≤k≤N,且S[k+1…k+M]=T[1…M],则匹配成功;否则失败。该算法最坏情况下要进行M*(N-M+1)次比较,时间复杂度为O(M*N)。下面结合图片,解释一下:

《浅谈字符串匹配算法—BF算法及KMP算法》

S代表源字符串,T代表我们要查找的字符串。BF算法可以表述如下:依次遍历字符串S,看是否字符串S中含有字符串T。因此,我们依次比较S[0] 和T[0]、S[1] 和T[1]、S[2] 和T[2]……S[n]和T[n] ,从图中我们可知,S[0]-S[7]和T[0]-T[7]依次相等。当匹配到S[8]和T[8]时,两个字符不等。根据定义,此时S和T都要回溯,T向右移动一个字符的位置,即S回溯到S[1]的位置,T回溯到T[0]的位置,再重新开始比较。此时,S[1]和T[0]、S[2]和T[1]……如果再次发现不匹配字符,则再次回溯,即S回溯到S[2]的位置,T回到T[0]的位置。循环往复,直到到达S或者T字符串的结尾。如果是到达S串的结尾,则表示匹配失败,如果是到达T串的结尾,则表示匹配成功。

BF算法优点:思想简单,直接,无需对字符串S和T进行预处理。缺点:每次字符不匹配时,都要回溯到开始位置,时间开销大。

下面是BF算法的代码实现:bf.c 

[cpp] 
view plain
copy
print
?

  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <string.h>  
  4.   
  5. int index_bf(char *s,char *t,int pos);  
  6. int index_bf_self(char *s,char *t,int index);  
  7.   
  8. /* 
  9. BF算法 示例 
  10. */  
  11. int main()  
  12. {  
  13.     char s[]=“6he3wor”//标准BF算法中,s[0]和t[0]存放的为字符串长度。  
  14.     char t[]=“3wor”;  
  15.     int m=index_bf(s,t,2); //标准BF算法  
  16.     printf(“index_bf:%d\n”,m);  
  17.     m=index_bf_self(s,t,2); //修改版BF算法,s和t中,不必再存放字符串长度。  
  18.     printf(“index_bf_self:%d\n”,m);  
  19.     exit(0);  
  20. }  
  21.   
  22. /* 
  23. 字符串S和T中,s[0],t[0]存放必须为字符串长度 
  24. 例:s[]=”7hi baby!”  T[]=”4baby”  index_bf(s,t,1); 
  25. pos:在S中要从下标pos处开始查找T 
  26. (说明:标准BF算法中,为研究方便,s[0],t[0]中存放的为各自字符串长度。) 
  27. */  
  28. int index_bf(char *s,char *t,int pos)  
  29. {  
  30.     int i,j;  
  31.     if(pos>=1 && pos <=s[0]-‘0’)  
  32.     {  
  33.         i=pos;  
  34.         j=1;  
  35.         while(i<=s[0]-‘0’&&j<=t[0]-‘0’)  
  36.         {  
  37.             if(s[i]==t[j])  
  38.             {  
  39.                 i++;  
  40.                 j++;  
  41.             }  
  42.             else   
  43.             {  
  44.                 j=1;  
  45.                 i=i-j+2;  
  46.             }  
  47.             if(j>t[0]-‘0’)  
  48.             {  
  49.                 return i-t[0]+‘0’;  
  50.             }  
  51.         }  
  52.         return -1;  
  53.     }  
  54.     else   
  55.     {  
  56.         return -1;  
  57.     }  
  58. }  
  59.   
  60. /* 
  61. 修改版,字符串s和t中,不必再包含字符串长度。 
  62. 例:s[]=”hi baby”  t[]=”baby”  index_bf_self(s,t,0); 
  63. index:在s中,从几号下标开始查找 
  64. */  
  65. int index_bf_self(char *s,char *t,int index)  
  66. {  
  67.     int i=index,j=0;  
  68.     while(s[i]!=‘\0’)  
  69.     {  
  70.         while(*(t+j)!=‘\0’ && *(s+i+j)!=‘\0’)  
  71.         {  
  72.             if(*(t+j)!=*(s+i+j))  
  73.                 break;  
  74.             j++;  
  75.         }  
  76.         if(*(t+j)==‘\0’)  
  77.         {  
  78.             return i;  
  79.         }  
  80.         i++;  
  81.         j=0;  
  82.     }  
  83.     return -1;  
  84. }  

测试结果:

《浅谈字符串匹配算法—BF算法及KMP算法》

二、KMP算法:

由BF算法例图中可知,当S[8]和T[8]不匹配时,S和T都需要回溯,时间复杂度高。因此,出现了KMP算法。先看下图:

《浅谈字符串匹配算法—BF算法及KMP算法》

从图中,我们可以很容易的发现,S不必回溯到S[1]的位置,T也不必回溯到T[0]的位置,因为前面的字符,S和T中都是相等的。如果S不回溯的话,那T该怎么办呢?我们也可以很容易的发现,S中5、6、7号字符和T中0、1、2号字符是相等的。故T直接回溯到T[3]的位置即可。此时我们就省去了很多不必要的回溯和比较。那么这些都是我们从图中直观得出的结论,如果换做其他字符,我们又如何知道T该回溯到几号字符呢?

先看看KMP算法的思想:假设在模式匹配的进程中,执行T[i]和W[j]的匹配检查。若T[i]=W[j],则继续检查T[i+1]和W[j+1]是否匹配。若T[i]<>W[j],则分成两种情况:若j=1,则模式串右移一位,检查T[i+1]和W[1]是否匹配;若1<j<=m,则模式串右移j-next(j)位,检查T[i]和W[next(j)]是否匹配。重复此过程直到j=m或i=n结束。(摘抄自百度百科)      不知其他人怎样,反正我是一看这些就晕。说的简单点,KMP算法的重点就是研究当某个字符不匹配时,T该回溯到什么位置去,以避免多余的回溯和比较过程。如果要确定字符不匹配时,T应该回溯到什么位置,那么我们就要对T字符串进行预处理。这里,我们用一个int型的 next 数组来标识T中当各个元素失配时,T 下标应该回溯到的位置。下面,还是用一张图片讲解:

《浅谈字符串匹配算法—BF算法及KMP算法》

图中,首先构造 Next 数组,构造过程见图解(这里讲解的简单了些,本文重点是理清KMP算法思路,故没有赘述,想细究的同学,自己谷歌一下吧)。构造完毕后,当S[8]和T[8]失配时,我们从next 数组可知,T应回溯到T[3]的位置,重新开始比较。样子有点像下面这样:

《浅谈字符串匹配算法—BF算法及KMP算法》

如果S[8]和T[3]再次失配,则继续回溯,即比较S[8]和T[0]。如果再次失配,T已无回溯元素可言,此时,S向后移动,即开始比较S[9]和T[0]……结束条件就是:到达字符串S或者T的结尾。若是S结尾,则返回-1.若是T结尾,则匹配成功。返回S中T串开始时的下标即可。

下面给出个小例子,仅供大家练习使用:

《浅谈字符串匹配算法—BF算法及KMP算法》

下面给出KMP算法标准代码(即数组首元素保存的是字符串长度):kmp.c

[cpp] 
view plain
copy
print
?

  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3.   
  4. void get_next(char *t,int *next);  
  5. int index_kmp(char *s,char *t,int pos);  
  6.   
  7. int main(int argc,char *argv[])  
  8. {  
  9. /* 
  10.     char t[]=”6ababcd”; 
  11.     int next[7]; 
  12.     get_next(t,next); 
  13.     int i; 
  14.     for(i=0;i<7;i++) 
  15.         printf(“%d,”,next[i]); 
  16.     printf(“\n”); 
  17. */  
  18.     char s[]=“6helwor”;  
  19.     char t[]=“3wor”;  
  20.     int m=index_kmp(s,t,1);  
  21.     printf(“%d\n”,m);  
  22.     exit(0);  
  23. }  
  24.   
  25. /* 
  26. 利用KMP算法,求解字符串t在s中的开始位置。 
  27. pos:在字符串S中,从下标pos开始查找是否含有t子串 
  28. 如果含有,返回t在s中的下标起始位置。否则,返回-1. 
  29. 注意:s和t中首元素保存的都是字符串的长度。 
  30. */  
  31. int index_kmp(char *s,char *t,int pos)  
  32. {  
  33.     int next[sizeof(t)];   
  34.     get_next(t,next);  
  35.     int i=pos;  
  36.     int j=1;  
  37.     while(i<=s[0]-‘0’&&j<=t[0]-‘0’)  
  38.     {  
  39.         if(0==j || s[i]==t[j])  
  40.         {  
  41.             i++;  
  42.             j++;  
  43.         }  
  44.         else   
  45.         {  
  46.             j=next[j];  
  47.         }  
  48.     }  
  49.     if(j>t[0]-‘0’)  
  50.     {  
  51.         return i-t[0]+‘0’;  
  52.     }  
  53.     else  
  54.         return -1;  
  55. }  
  56.   
  57. void get_next(char *t,int *next)  
  58. {  
  59.     int i=0;  
  60.     int j=1;  
  61.     next[1]=0;  
  62.     while(j<t[0]-‘0’)  
  63.     {  
  64.         if(0==i || t[i]==t[j])  
  65.         {  
  66.             i++;  
  67.             j++;  
  68.             if(t[i]!=t[j])  
  69.                 next[j]=i;  
  70.             else  
  71.                 next[j]=next[i];  
  72.         }  
  73.         else   
  74.         {  
  75.             i=next[i];  
  76.         }  
  77.     }   
  78. }  

示例测试结果:

《浅谈字符串匹配算法—BF算法及KMP算法》

修改版KMP算法:(字符数组首元素不再保存字符串长度,更符合实际应用) kmp2.c

[cpp] 
view plain
copy
print
?

  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <string.h>  
  4.   
  5. void get_next(char *t,int *next);  
  6. int index_kmp(char *s,char *t,int index);  
  7.   
  8. int main(void)  
  9. {  
  10.   char s[]=“hello world!”;  
  11.   char t[]=“world”;  
  12.   /* 
  13.   int next[strlen(t)]; 
  14.   get_next(t,next); 
  15.   int i; 
  16.   for(i=0;i<strlen(t);i++) 
  17.   { 
  18.     printf(“%d,”,next[i]); 
  19.   } 
  20.   */  
  21.   int m=index_kmp(s,t,0);  
  22.   printf(“index:%d\n”,m);  
  23.   exit(0);  
  24. }  
  25.   
  26. /* 
  27. 在字符串s中,从下标index开始查找是否含有字符串t.如果有,返回t在s中的开始位置;如果没有,返回-1。 
  28. (使用KMP算法实现) 
  29. 注:字符数组s和t中,不再保存字符串长度。 
  30. */  
  31. int index_kmp(char *s,char *t,int index)  
  32. {  
  33.   int next[strlen(t)];  
  34.   get_next(t,next);  
  35.   int i=index,j=0;  
  36.   while(s[i]!=‘\0’ && t[j]!=‘\0’)  
  37.     {  
  38.       if(s[i]==t[j])  
  39.         {  
  40.           i++;  
  41.           j++;  
  42.           continue;  
  43.         }  
  44.       else   
  45.         {  
  46.           j=next[j]; //从模式匹配数组中,获取要回溯到的结点  
  47.         }  
  48.       if(0==j) //单独处理第一个字符  
  49.         {  
  50.           if(s[i]==t[j])  
  51.             {  
  52.               i++;  
  53.               j++;  
  54.             }  
  55.           else  
  56.             {  
  57.               i++;  
  58.             }  
  59.         }  
  60.     }  
  61.   if(t[j]==‘\0’//表示字符串t中,所有字符已匹配完毕  
  62.     {  
  63.       return i-strlen(t); //因为i以匹配至s中t字符串的结尾。因为要返回的是s中t的开始下标,故i-strlen(t).  
  64.     }  
  65.   else   
  66.     {  
  67.       return -1;  
  68.     }  
  69. }  
  70.   
  71. /* 
  72. KMP算法之next数组代码 
  73. next数组定义:当模式匹配串T失配的时候,next数组对应的元素知道应该用T串的哪个元素进行下一轮的匹配。 
  74. */  
  75. void get_next(char *t,int *next)  
  76. {  
  77.   int i=0; //Prefix 前缀  
  78.   int j=1; //Postfix 后缀  
  79.   next[0]=0; //自定义的,0和1都从0开始匹配  
  80.   next[1]=0;  
  81.   while(t[j]!=‘\0’)  
  82.     {  
  83.       if(t[i]==t[j]) //若前后字符匹配,则向前推进  
  84.         {  
  85.           i++;  
  86.           j++;  
  87.           next[j]=i;  
  88.           continue;  
  89.         }  
  90.       else  
  91.         {  
  92.           i=next[i]; //前后字符不匹配,则回溯。注意,此时是i和j不匹配,因此,根据next数组定义,要回溯到next[i]的值。  
  93.         }  
  94.       if(0==i) //当回溯到首字符时,单独进行处理  
  95.         {  
  96.           if(t[i]==t[j])  
  97.             {  
  98.             next[++j]=++i;  
  99.             }  
  100.           else  
  101.             next[++j]=i;  
  102.         }  
  103.     }  
  104. }  

示例测试结果:

《浅谈字符串匹配算法—BF算法及KMP算法》

    原文作者:KMP算法
    原文地址: https://blog.csdn.net/xishuang_gongzi/article/details/48470859
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞