Chinaunix首页 | 论坛 | 博客
  • 博客访问: 3883052
  • 博文数量: 146
  • 博客积分: 3918
  • 博客等级: 少校
  • 技术积分: 8585
  • 用 户 组: 普通用户
  • 注册时间: 2010-10-17 13:52
个人简介

个人微薄: weibo.com/manuscola

文章分类

全部博文(146)

文章存档

2016年(3)

2015年(2)

2014年(5)

2013年(42)

2012年(31)

2011年(58)

2010年(5)

分类: C/C++

2011-09-25 11:06:35

     Boyer-Moore算法是一个文本字符串搜索算法。和暴力搜索算法相比,它充分利用待搜索字符串的一些特征,加快了搜索的步骤。

    首先来介绍基本概念。我们待搜素的文本称之为 text,我们想寻找的字符串叫pattern。显然文本text很长,我们从很长的文本搜索我们关心的关键字pattern。

    首先最容易想到的办法就是暴力搜索,从头到尾,挨个比较,如果和pattern字符串不一致,右移一位,再次比较。

  1. int Naive_search(char text[],int textlen,char pattern[],int patternlen)
  2. {
  3.     int i ;
  4.     int find = 0;
  5.     for(i = 0;i<(textlen - patternlen);i++)
  6.     {
  7.          if(memcmp(&(text[i]),pattern,patternlen) == 0)
  8.          {
  9.                    find++;
  10.                    FindThePattern(text,pattern,i);
  11.          }

  12.     }
  13.         return find;
  14. }
     暴力搜索最容易想到,但是它的效率太低。本文下面介绍Boyer-Moores算法。
     Boyer-Moore的思想是这样的,通过预处理,获取pattern字符串的一些特征,通过这些特征,来减少不必要的比较。Boyer-Moore主要有两个启发策略来减少不必要的比较。
     Boyer-Moore 算法扫描比较字符串是从右向左比较。和普通的从左到右的习惯有写不同,这种扫描有什么好处,大家理解了这个算法就明白了。

1   Bad Character  坏字符
    看下面的字符串搜索,在文本“abbadabacbmnpbac”中搜索babac,我们按照从右到左的比较,text为d,而pattern为c,不匹配,按照暴力搜索的思想,我们应该右移一位,继续比较,如下图中的naive。再次比较text中的a 和pattern中的c。
    但是我们注意观察下,就可以发现,上一轮比较中text中的d,在pattern字符串中根本就不存在,你右移一位,现在pattern中“babac”的倒数第二位a需要和text中的进行比较。很明显仍然不可能匹配,因为pattern中根本就没有d这个字符,无论那一位和d比较,都不会匹配,所以右移一位太保守,并没有充分利用pattern中的信息。看下图中BM哪一行,直接向右移动patternlen位。

   OK,根据BM算法,我们安全移动到了第九位。发现text 为b,pattern为 c,不匹配,很不幸,这次b这个字符在pattern中存在,我们是不是只能移动一位呢。不一定,我们看到必须试图寻找和文本中的9位置的b重合的位置,pattern中的存在两个b。安全的策略是pattern最右边b的和*位置的b匹配,
换句话说,可以右移两位,如果右移4为,那就太激进了,容易漏掉某些匹配的项。

----------------------------------------------------------------------------------------
  
             0 1 2 3 4 5 6 7 8 9 A B C D E F
text         a b b a d a b a c b m n p b a c  
pattern      b a b a c
naive          b a b a c                         (太保守,进行不必要的比较)
BM                     b a b a c 
BM                             b a b a c         (太激进,可能漏掉)
BM                         b a b a c 


                                      *             
  1. text          x y a c d e f
  2. pattern       a q b c d e f
  3. bm                a q b c d e f
详解:
f e d c 都已经匹配,第一个不匹配的是a,寻找pattern最右边的a与text中的a对齐。


text            x y c c d e f
pattern         c a b c d e f
BM            c a b c d e f         错误的办法(走了回头路)
BM                c a b c d e f
详解:
f e d c都已经匹配,按照前面的方法,应该寻找pattern最右边的c和text的蓝色的c对齐,但是pattern左移才能满足最右边的c与text中c对齐,这种情况下,肯定不能走回头路,简单的右移一位就可以了。
  
----------------------------------------------------------------------------------------
    通过上面的分析我们可以看出,要想利用坏字符的启发策略,我们需要几下每个字符最右边一次出现的位置。
   1  没出现过的字符一律定为pattern的长度。
   2  pattern中出现过的字符,最后一次出现该字符的位置 i。

    下面给出函数
  1. #define __CHAR_MAX (255)

  2. int preBM_bad(char* pattern ,int len,int rightmost_occur[])
  3. {
  4.     int i;
  5.     for(i = 0;i<__CHAR_MAX;i++)
  6.         rightmost_occur[i] = len;
  7.     for(i = 0;i<len;i++)
  8.     {
  9.         rightmost_occur[pattern[i]] = i;
  10.     }
  11.     return 0;
  12. }

2 好后缀启发策略 good suffix shift

    假如说,pattern的后u个字符和text都已经匹配了,但是接下来的一个字符不匹配,如下图所示,我需要移动才能匹配。如果说后u个字符在pattern其他位置也出现过,这种情况非常好,我们将pattern右移到前面的u个字符和最后的u个字符相同
text           a b w u t u q y x a b c d m n p q x
pattern        n w a b c d p m n a b c d 
BM                           n w a b c d p m n a b c d                                               



    另外一种情况是pattern 的最前r个字符和最后r个字符是一模一样的,这种情况也会给我们带来额外的信息。
             
                                               
                                                      *  
text           a b w u t u q m n a b c d m n p q x
pattern            a b c d p m n a b c d 
BM                               a b c d p m n a b c d

 为了利用good suffix,我们需要先计算suffix。
1  suffix[patternlen-1] = patternlen;
2  suffix[i] = k  
    for [ pattern[i-k+1] ....,pattern[i]] == [pattern[patternlen-1-k+1],pattern[patternlen-1]] 
   如下图,

 

  1. pattern n w a b c d p m n a b c d
  2. suffix  0 0 0 0 0 4 0 0 0 0 0 0 13
 


上代码
  1. int calc_goodsuffix(char* pattern,int len,int suffix[])
  2. {
  3.     int i,j,k;
  4.     int result;
  5.     suffix[len-1] = len;
  6.     
  7.     for(i = len-2;i>=0;i--)
  8.     {
  9.         k = i;
  10.         j=len-1;
  11.         result = 0;
  12.         while(pattern[k--]==pattern[j--])
  13.         {
  14.             result++;
  15.         }
  16.         suffix[i] = result;
  17.     }
  18.     return 0;
  19. }

  20. int preBM_good(char* pattern ,int len,int goodskip[])
  21. {
  22.     int i,j;
  23.     int *suffix = malloc(len*sizeof(int));
  24.     if(suffix == NULL)
  25.     {
  26.         printf("malloc failed for preBM_good\n ");
  27.         return -1;
  28.     }
  29.     
  30.     calc_goodsuffix(pattern,len,suffix);

  31.     for(i = 0;i<len;i++)
  32.     {
  33.           goodskip[i] = len;
  34.     }
  35.     j = 0;
  36.     /*最前和最后的i+1个字符一致*/
  37.     for(i = len-2;i>=0;--i)
  38.     {
  39.         if(suffix[i] == i+1) /*consider the pattern "ABCDMNPABCD"*/
  40.         {
  41.            for(;j<len-1-i;j++)
  42.            {
  43.              if(goodskip[j] == len) /*consider the pattern BBBBMNPBBBB*/
  44.               {
  45.                  goodskip[j] = len-1-i;
  46.               }
  47.             }
  48.         }
  49.     }
  50.      
  51.     for(i = 0;i<len-2;i++)
  52.     {
  53.           goodskip[len-1-suffix[i]] = len-1-i;
  54.     }

  55.     if(suffix)
  56.        free(suffix);
  57.     return 0;
  58.     
  59. }
最后给出boyer-moore算法的主函数
  1. int BM_search(char* text,int textlen,char* pattern,int patternlen)
  2. {
  3.     int i,j;
  4.     int ret;
  5.     int rightmost[__CHAR_MAX];
  6.     int find = 0;
  7.     int badskip;
  8.     
  9.     int *goodskip = malloc(sizeof(int)*patternlen) ;
  10.     if(goodskip == NULL)
  11.     {
  12.         printf("malloc for goodskip failed\n");
  13.         return -1;
  14.     }
  15.     
  16.     preBM_bad(pattern ,patternlen,rightmost);
  17.     ret = preBM_good(pattern,patternlen,goodskip);
  18.     if(ret != 0)
  19.     {
  20.         printf("preBM_good failed\n");
  21.         return -2;
  22.     }
  23.     
  24.     j = 0;
  25.     while(j < textlen - patternlen)
  26.     {
  27.         for(i = patternlen-1;i>=0 && pattern[i] == text[j+i];i--)
  28.         {
  29.          ;
  30.         }
  31.         if(i<0)
  32.         {
  33.               FindThePattern(text,pattern,j);
  34.               j += goodskip[0];
  35.               find++;
  36.         }
  37.         else
  38.         {
  39.             if(i - rightmost[text[j+i]] >0)
  40.                 badskip = i - rightmost[text[j+i]];
  41.             else
  42.                 badskip = 1;/*不走回头路*/
  43.             
  44.             j+=Max(goodskip[i],badskip);
  45.         }
  46.     }
  47.     
  48.     if(goodskip)
  49.     {
  50.           free(goodskip);
  51.     }
  52.     return find;
  53. }

参考文献:
1 
3  Michael Abrash Graphics Programming Black Book







阅读(13032) | 评论(6) | 转发(4) |
给主人留下些什么吧!~~

GFree_Wind2011-10-21 12:40:09

今天也看到了这个算法。我有一个问题啊,这个算法在考虑坏字符的时候,是针对一个字母计算的。如果考虑更多的字符,比如2个,那么可以移位就更多了。当然占得内存也越多。

Bean_lee2011-10-13 22:02:44

GFree_Wind: 这个算法的思想,一是利用坏字符,另外一个是patter本身的特性,来避免无谓的比较——不知道我理解的对否?另外,不知道这个算法与KPM比较,哪个更快?.....
BM 算法好像要优于KMP算法,Sunday算法好像更好,而且Sunday算法异常简单。最先学习的BM算法,后来看到Sunday算法,实现要简单很多,而且效率高。可惜已经没心情写一篇Sunday算法的实现了。

几种算法的比较,没有编码,没有统计数据,不敢说的太绝对。

GFree_Wind2011-10-11 12:39:14

这个算法的思想,一是利用坏字符,另外一个是patter本身的特性,来避免无谓的比较——不知道我理解的对否?另外,不知道这个算法与KPM比较,哪个更快?

Bean_lee2011-09-26 23:12:00

blacksapper: 这个是字符串比较最快的方法之一.....
sunday 算法好像BM算法要简单,而且性能要好。

blacksapper2011-09-26 17:28:02

这个是字符串比较最快的方法之一