BM算法详解

BM算法

  后缀匹配,是指模式串的比较从右到左,模式串的移动也是从左到右的匹配过程,经典的BM算法其实是对后缀蛮力匹配算法的改进。为了实现更快移动模式串,BM算法定义了两个规则,好后缀规则和坏字符规则,如下图可以清晰的看出他们的含义。利用好后缀和坏字符可以大大加快模式串的移动距离,不是简单的++j,而是j+=max (shift(好后缀), shift(坏字符))

  先来看如何根据坏字符来移动模式串,shift(坏字符)分为两种情况:

  • 坏字符没出现在模式串中,这时可以把模式串移动到坏字符的下一个字符,继续比较,如下图:

  • 坏字符出现在模式串中,这时可以把模式串第一个出现的坏字符和母串的坏字符对齐,当然,这样可能造成模式串倒退移动,如下图:

  此处配的图是不准确的,因为显然加粗的那个b并不是”最 靠右的”b。而且也与下面给出的代码冲突!我看了论文,论文的意思是最右边的。当然了,尽管一时大意图配错了,论述还是没有问题的,我们可以把图改正一 下,把圈圈中的b改为字母f就好了。接下来的图就不再更改了,大家心里有数就好。
  为了用代码来描述上述的两种情况,设计一个数组bmBc[‘k‘],表示坏字符‘k’在模式串中出现的位置距离模式串末尾的最大长度,那么当遇到坏字符的时候,模式串可以移动距离为: shift(坏字符) = bmBc[T[i]]-(m-1-i)。如下图:

  数组bmBc的创建非常简单,直接贴出代码如下:

 1 void preBmBc(char *x, int m, int bmBc[]) {
 2
 3    int i;
 4
 5    for (i = 0; i < ASIZE; ++i)
 6
 7       bmBc[i] = m;
 8
 9    for (i = 0; i < m - 1; ++i)
10
11       bmBc[x[i]] = m - i - 1;
12
13 }

  代码分析:

  • ASIZE是指字符种类个数,为了方便起见,就直接把ASCII表中的256个字符全表示了,哈哈,这样就不会漏掉哪个字符了。
  • 第一个for循环处理上述的第一种情况,这种情况比较容易理解就不多提了。第二个for循环,bmBc[x[i]]中x[i]表示模式串中的第i个字符。bmBc[x[i]] = m - i - 1;也就是计算x[i]这个字符到串尾部的距离。
  • 为什么第二个for循环中,i从小到大的顺序计算呢?哈哈,技巧就在这儿了,原因在于就可以在同一字符多次出现的时候以最靠右的那个字符到尾部距离为最终的距离。当然了,如果没在模式串中出现的字符,其距离就是m了。

再来看如何根据好后缀规则移动模式串,shift(好后缀)分为三种情况:

  • 模式串中有子串匹配上好后缀,此时移动模式串,让该子串和好后缀对齐即可,如果超过一个子串匹配上好后缀,则选择最靠左边的子串对齐。

  • 模式串中没有子串匹配上后后缀,此时需要寻找模式串的一个最长前缀,并让该前缀等于好后缀的后缀,寻找到该前缀后,让该前缀和好后缀对齐即可。

  • 模式串中没有子串匹配上后后缀,并且在模式串中找不到最长前缀,让该前缀等于好后缀的后缀。此时,直接移动模式到好后缀的下一个字符。

  为了实现好后缀规则,需要定义一个数组suffix[],其中suffix[i] = s 表示以i为边界,与模式串后缀匹配的最大长度,如下图所示,用公式可以描述:满足P[i-s, i] == P[m-s, m]的最大长度s。

  构建suffix数组的代码如下:

 1 void suffixes(char *x, int m, int *suff)
 2  {
 3   suff[m-1]=m;
 4   for (i=m-2;i>=0;--i){
 5     q=i;
 6     while(q>=0&&x[q]==x[m-1-i+q])
 7         --q;
 8     suff[i]=i-q;
 9 }
10 }

  ·注解:这一部分代码乏善可陈,都是常规代码,这里就不多说了。

  有了suffix数组,就可以定义bmGs[]数组,bmGs[i] 表示遇到好后缀时,模式串应该移动的距离,其中i表示好后缀前面一个字符的位置(也就是坏字符的位置),构建bmGs数组分为三种情况,分别对应上述的移动模式串的三种情况

  • 模式串中有子串匹配上好后缀

  • 模式串中没有子串匹配上好后缀,但找到一个最大前缀

  • 模式串中没有子串匹配上好后缀,但找不到一个最大前缀

  构建bmGs数组的代码如下:

 1 void preBmGs(char *x, int m, int bmGs[]) {
 2    int i, j, suff[XSIZE];
 3    suffixes(x, m, suff);
 4    for (i = 0; i < m; ++i)
 5       bmGs[i] = m;
 6    j = 0;
 7    for (i = m - 1; i >= 0; --i)
 8       if (suff[i] == i + 1)
 9          for (; j < m - 1 - i; ++j)
10             if (bmGs[j] == m)
11                bmGs[j] = m - 1 - i;
12    for (i = 0; i <= m - 2; ++i)
13       bmGs[m - 1 - suff[i]] = m - 1 - i;
14 }

  注解:这一部分代码挺有讲究,写的很巧妙,这里谈谈我的理解。讲解代码时候是分为三种情况来说明的,其实第二种和第三种可以合并,因为第三种情况相当于与好后缀匹配的最长前缀长度为0。由于我们的目的是获得精确的bmGs[i],故而若一个 字符同时符合上述三种情况中的几种,那么我们选取最小的bmGs[i]。比如当模式传中既有子串可以匹配上好后串,又有前缀可以匹配好后串的后串,那么此 时我们应该按照前者来移动模式串,也就是bmGs[i]较小的那种情况。故而每次修改bmGs[i]都应该使其变小,记住这一点,很重要!而在这三种情况中第三种情况获得的bmGs[i]值大于第二种大于第一种。故而写代码的时候我们先计算第三种情况,再计算第二种情况,再计算第一种情况。为什么呢,因为对于同一个位置的多次修改只会使得bmGs[i]越来越小。

  • 代码4-5行对应了第三种情况,7-11行对于第二种情况,12-13对应第三种情况。
  • 第三种情况比较简单直接赋值m,这里就不多提了。
  • 第二种情况有点意思,咱们细细的来品味一下。

   1. 为什么从后往前,也就是i从大到小?原因在于如果i,j(i>j)位置同时满足第二种情况,那么m-1-i<m-1-j,而第十行代码保证了每个位置最多只能被修改一次,故而应该赋值为m-1-i,这也说明了为什么要从后往前计算。

   2. 第8行代码的意思是找到了合适的位置,为什么这么说呢?因为根据suff的定义,我们知道x[i+1-suff[i]…i]==x[m-1-siff[i]…m-1],而suff[i]==i+1,我们知道x[i+1-suff[i]…i]=x[0,i],也就是前缀,满足第二种情况。

3. 第9-11行就是在对满足第二种情况下的赋值了。第十行确保了每个位置最多只能被修改一次。

  • 第12-13行就是处理第一种情况了。为什么顺序从前到后呢,也就是i从小到大?原因在于如果suff[i]==suff[j],i<j,那么m-1-i>m-1-j,我们应该取后者作为bmGs[m - 1 - suff[i]]的值。

  再来重写一遍BM算法:

 1 void BM(char *x, int m, char *y, int n) {
 2    int i, j, bmGs[XSIZE], bmBc[ASIZE];
 3
 4    /* Preprocessing */
 5    preBmGs(x, m, bmGs);
 6    preBmBc(x, m, bmBc);
 7
 8    /* Searching */
 9    j = 0;
10    while (j <= n - m) {
11       for (i = m - 1; i >= 0 && x[i] == y[i + j]; --i);
12       if (i < 0) {
13          OUTPUT(j);
14          j += bmGs[0];
15       }
16       else
17          j += MAX(bmGs[i], bmBc[y[i + j]] - m + 1 + i);
18    }
19 }

时间: 2024-10-29 19:05:43

BM算法详解的相关文章

BM算法详解(转)

1977 年,Robert S.Boyer和J Strother Moore提出了另一种在O(n)时间复杂度内,完成字符串匹配的算法,其在绝大多数场合的性能表现,比KMP算法还要出色,下面我们就来详细了解一下这 一出色的单模式匹配算法,在此之前推荐读者读一下我的另一篇文章<KMP算法详解>,对于透彻理解BM算法大有裨益. 在讲解Boyer-Moore算法之前,我们还是要提一提KMP算法的老例子,当模式串与目标串匹配至如下位置时:  1  2  3  4  5  6  7  8  9 10 11

EM算法(3):EM算法详解

目录 EM算法(1):K-means 算法 EM算法(2):GMM训练算法 EM算法(3):EM算法详解

[转] KMP算法详解

转载自:http://www.matrix67.com/blog/archives/115 KMP算法详解 如果机房马上要关门了,或者你急着要和MM约会,请直接跳到第六个自然段.    我们这里说的KMP不是拿来放电影的(虽然我很喜欢这个软件),而是一种算法.KMP算法是拿来处理字符串匹配的.换句话说,给你两个字符串,你需要回答,B串是否是A串的子串(A串是否包含B串).比如,字符串A="I'm matrix67",字符串B="matrix",我们就说B是A的子串.

[搜索]波特词干(Porter Streamming)提取算法详解(3)

 接上 [搜索]波特词干(Porter Streamming)提取算法详解(2) 下面分为5大步骤来使用前面提到的替换条件来进行词干提取. 左边是规则,右边是提取成功或者失败的例子(用小写字母表示). 步骤1 SSES -> SS                   caresses  ->  caress IES  -> I                          ponies    ->  poni ties      ->  ti SS   -> S

KMP算法详解(图示+代码)

算法过程非常绕,不要企图一次就能看明白,多尝试就会明白一些.下面试图用比较直观的方法解释这个算法,对KMP算法的解释如下: 1. 首先,字符串"BBC ABCDAB ABCDABCDABDE"的第一个字符与搜索词"ABCDABD"的第一个字符,进行比较.因为B与A不匹配,所以搜索词后移一位. 2. 因为B与A不匹配,搜索词再往后移. 3. 就这样,直到字符串有一个字符,与搜索词的第一个字符相同为止. 4. 接着比较字符串和搜索词的下一个字符,还是相同. 5. 直到字

安全体系(三)——SHA1算法详解

本文主要讲述使用SHA1算法计算信息摘要的过程. 安全体系(零)—— 加解密算法.消息摘要.消息认证技术.数字签名与公钥证书 安全体系(一)—— DES算法详解 安全体系(二)——RSA算法详解 为保证传输信息的安全,除了对信息加密外,还需要对信息进行认证.认证的目的有两:一是验证信息的发送者是合法的,二是验证信息的完整性.Hash函数就是进行信息认证的一种有效手段. 1.Hash函数和消息完整性 Hash函数也称为杂凑函数或散列函数,函数输入为一可变长度x,输出为一固定长度串,该串被称为输入x

php 二分查找法算法详解

一.概念:二分查找又称折半查找,优点是比较次数少,查找速度快,平均性能好:其缺点是要求待查表为有序表,且插入删除困难.因此,折半查找方法适用于不经常变动而查找频繁的有序列表.首先,假设表中元素是按升序排列,将表中间位置记录的关键字与查找关键字比较,如果两者相等,则查找成功:否则利用中间位置记录将表分成前.后两个子表,如果中间位置记录的关键字大于查找关键字,则进一步查找前一子表,否则进一步查找后一子表.重复以上过程,直到找到满足条件的记录,使查找成功,或直到子表不存在为止,此时查找不成功. 二.代

【转】AC算法详解

原文转自:http://blog.csdn.net/joylnwang/article/details/6793192 AC算法是Alfred V.Aho(<编译原理>(龙书)的作者),和Margaret J.Corasick于1974年提出(与KMP算法同年)的一个经典的多模式匹配算法,可以保证对于给定的长度为n的文本,和模式集合P{p1,p2,...pm},在O(n)时间复杂度内,找到文本中的所有目标模式,而与模式集合的规模m无关.正如KMP算法在单模式匹配方面的突出贡献一样,AC算法对于

支持向量机(SVM)(五)-- SMO算法详解

一.我们先回顾下SVM问题. A.线性可分问题 1.SVM基本原理: SVM使用一种非线性映射,把原训练            数据映射到较高的维.在新的维上,搜索最佳分离超平面,两个类的数据总可以被超平面分开. 2.问题的提出: 3.如何选取最优的划分直线f(x)呢? 4.求解:凸二次规划 建立拉格朗日函数: 求偏导数: B.线性不可分问题 1.核函数 如下图:横轴上端点a和b之间红色部分里的所有点定为正类,两边的黑色部分里的点定为负类. 设: g(x)转化为f(y)=<a,y> g(x)=