现在的位置: 首页 > 综合 > 正文

Boyer–Moore–Horspool文本匹配算法(BM算法的简化版)

2019年04月10日 ⁄ 综合 ⁄ 共 6037字 ⁄ 字号 评论关闭

英语里有句习语叫"find a needle in a haystack",译成中文叫"大海捞针"(原意是在一堆干草中寻找一根针)。计算机中的文本匹配(string matching)就是要解决怎样在一段很长的文本中找到符合要求的一个子串,该子串通常叫模式串(pattern),也就是对应我们要找的“针”。常用的文本精确匹配(exact  string matching)算法有蛮力法(brute-force),Boyer-Moore算法和KMP算法。后两种算法都是用空间换 时间的经典案例,不像蛮力法,文本的位置指针i不需要回退到已经匹配过的字符位置(BM算法的每次匹配虽然从右向左移动指针,但从来不会将i指针回退到已经匹配过的字符位置)。在最坏情况下的时间复杂度是与文本长度成线性关系的。特别需要说明的是,BM算法和本文中介绍的它的简化版本Horspool算 法,在最佳情况下的时间复杂度是O(n/m),因此对模式串比较长的情况(比如长度大于5)应用该算法比较有效。

本文介绍Boyer-Moore算法的一个简化版本,叫做Boyer–Moore–Horspool算法或直接叫做Horspool算法。

设文本T长度为n,模式串P长度为m。T的当前位置指针为i(0<=i<n),P的当前位置指针为j(0<=j<m)。

 

算法:
基本思想是模式串窗口沿着文本向右滑动,但是每次滑动的距离不能太大,否则会冒着遗漏可能成功匹配的字符串的风险。在模式串窗口滑动完成后,开始重新设置i和j指针,并且让i和j分别从右到左挨个字符匹配。匹配到某个字符位置时发现T[i]和P[j]不相同了(即发现了mismatch,字符T[i]也叫做bad  character),就要分以下三种情况讨论:

 

 

1)如果T[i]不在模式串中存在,需要将模式串窗口沿着文本向右滑动j+1个字符位置。这是因为j前面的任何字符P[0..j-1]都与T[i]不匹配,可以直接将P[0]跟i的下一个字符T[i+1]对齐用作下一次的匹配。
例如:


此时j=3,T[i]=T,P[j]=D,但是字符T[i]=T在模式串中没有出现。直接将P[0]=N跟i的下一个字符T [i+1]=L对齐用作下一次的匹配。所以下一次滑动窗口的位置如上所示。

2)如果T[i]在模式串中存在,分两种情况讨论:

case 2a) 设P中最右边出现的字符T[i]所处的位置为q,但是q>j,这时如果还要让T[i]跟P[q]对齐的话,势必要让模式串窗口沿着文本向左移动,显然这是做无用功。所以正确而且最保守的做法是将模式串窗口沿着文本向右移动1个字符位置。
例如:
 
此时j=4,T[i]=E,P[j]=L,这里字符T[i]=E在模式串中最右边位置q=5(注意必须是最右边位置,左边的两个E不算),因为q=5>j=4,所以最保守的情况是将P只向右滑动一个字符位置,如上图所示。

case 2b)如果q<j (显然q不可能等于j了,因为T[i] != P[j]),这是就需要将T[i]跟P[q]对齐,此时能保证模式串窗口会向右滑动。模式窗口向右滑动的字符个数为j-q。
例如:

此时j=4,T[i]=D,P[j]=L,这里字符T[i]=D在模式串中最右边位置q=3,因为q=3<=j=4,所以将模式窗 口向右滑动j-q=1个字符以保持T[i]=D和j左边的第一个字符D对齐,如上图所示。

注意在下一次匹配时,需要调整j到模式串的末尾字符位置,并且i和j对齐。可用公式得出:
  i <- i+m-min(j,1+q)
  j <- m -1
二者顺序不能颠倒,因为计算i时需要j的原来值。

根据上面的讨论前提是需要知道q的值,即解决这样一个问题:某个字符c(即bad character)在模式串P中最右边出现的位置是多少?考虑到文本中这样的c可能为任意字符,需要预先计算一张表(叫做bad- character shift表, 表的索引值范围即字符集的所有字符值。具体实现时,如果c在模式串中不出现, 该表对应预设值-1。参考下面的实现。

算法复杂度:Horspool算法在平均情况下的时间复杂度是O(n),但是在最坏情况下的复杂度跟蛮力法在同一个数量级,即O(mn),非简化版本的BM算法在最坏情况下的复杂度是线性O(n)。本博客另外一篇文章将介绍原始的BM算法(BM算法是1977年发明的,而Horspool算法是1980提出来的)如何在最坏的情况下复杂度跟KMP算法同级即O(n), 。

最坏情况:
例如文本中只含一个字母:T=AAAAAAAAA...AA, 模式串P=BAAA。应用在本算法中的case 2a每次只将模式串沿文本向右滑动一个字符。这跟蛮力法的操作次数一样多。

最佳情况:
文本T=After a long text, here's a needle ZZZZZ
模式串P=ZZZZZ
匹配的过程如下(注意每次i和j指针跳跃距离都是模式串长度m):

                    
实现:

 

测试结果:

抱歉!评论已关闭.