Boyer-Moore 算法详解
来源:互联网 发布:本子作者知乎 编辑:程序博客网 时间:2024/05/02 04:45
写在前面:我的本意是想以通俗的语言来介绍Boyer-Moore,可是“学术”一点的语言毕竟有它存在的合理性,所以,额……
我的另一个意图是想详细介绍good-suffix shift的计算方法——因为中文世界中看不到对其完整的介绍:涉及到good-suffix的地方不是被直接跳过,就是仅仅给出一段代码——而这个部分的算法其实相对于Boyer-Moore本身来说更是精巧。
可惜对 Good-suffix 计算的介绍写出来之后因为格式比较复杂,又正赶上CSDN封图自宫,不易发表在博客上,故导出为PDF,感兴趣者可以前往查看:
http://docs.google.com/leaf?id=0B9sqyhyu5n-UM2ZmMzYxMTYtZDY5YS00ZTE5LWIxMTYtYTcyMmJiY2M3ODQ1&hl=zh_CN
BTW. Boyer-Moore这算法对我而言算是复杂的了,可是在 grep 的 src/kwset.c 中我看到了这样的东西:/* Build the Boyer Moore delta. Boy that's easy compared to CW. */,很是郁闷,下一步就看看CW感受一下去。
下面正式开始——
Boyer-Moore算法是Bob Boyer与J Strother Moore在1977年提出的一种字符串严格匹配(Exact String Matching)算法,它据说是常规应用中效率最高的[3],其时间复杂度可以达到亚线性,而且对于没有周期规律的模式串,最差情况下也只需要3n次比较。
约定和术语
约定
字符串和数组的下标均以0为起始,下标为负代表倒数
变量使用斜体
强调使用粗体
在区间的表示中,S[ a : b ] 代表在S处于区间 [ a, b ) 的部分
在区间的表示中,S[ a .. b ] 代表S处于区间 [ a, b ] 的部分
术语
pattern:模式串,即要查找的目标
m: pattern的长度
text:文本串,字符串匹配算法将在text中查找pattern出现的位置
n: text的长度
一、原理
1.1、概述
Boyer-Moore算法从右向左扫描模式串中的字符;当遇到不匹配的字符时,它通过预先计算的Bad-character跳转表以及Good-suffix跳转表寻找最大的跳转长度。
其思想简单表示如下:
计算bad-character跳转表
计算good-suffix跳转表
n ← |text|
m ← |pattern|
j ← 0
While j ≤ n - m:
从右向左匹配pattern 与text的子串text[ j : j+m ]
若匹配成功:
报告一次成功匹配
令 j ← j + good-suffix-table[0]
否则:
根据匹配失败的位置i得到good-suffix跳转长度;
根据匹配失败的位置i和导致匹配失败的字符c得到
bad-character跳转的长度
比较上面两个长度,选择较大的一个,令其为k
令 j ← j + k
1.2、Bad-character跳转原理说明
当匹配过程中遇到了不匹配的字符时,可以移动窗口使文本串中不匹配的字符a与模式串中字符a最后出现的位置对齐。
考虑如下情况:
text
a
u
pattern
b
u
图1.1 不匹配的情况
将模式串中的a与文本串中的对齐,我们得到:
text
a
u
pattern
a
不
包
含
a
图1.2 发生不匹配,且pattern中含有a
而若a在模式串中不存在,我们则可以将窗口移动到a出现的位置之后:
Text
a
u
pattern
不
包
含
a
图1.3 发生不匹配,且pattern中不含有a
这样做的意义很明显:
text
a
u
pattern
a
不
包
含
a
1
a
不
包
含
a
2
a
不
包
含
a
3
a
不
包
含
a
4
图1.4 常规的窗口移动方法
如图1.4所示,若第一次匹配在遇到字符a的时候失败了,那么按照常规的、顺序的窗口移动方法,第2次和第3次尝试也不可能得到正确的匹配,而只有将pattern中的a与text对齐,才有可能实现正确的匹配。
同样的道理,若整个pattern中不含有a,则可以安全的将窗口移动到a出现的位置之后。
通过将每个字符最后出现的位置记录在表bcTable中(没有出现的字符则令其处于-1位置),可以方便的将不匹配字符与模式串中该字符出现的最后位置对齐;因此定义bcTable为:
对于字符集中的每一个字符c:bcTable[c] = max{i : 0 ≤ i < m 且 pattern[i]=c}若c存在于pattern中,其他情况为 -1。
注意Bad-character跳转表中记录的只是每个字符最后出现的地方,因此不难观察发现,对于多次出现的字符,bad-character跳转表反而可能导致负的跳转;为此,[2] 提出了一种“扩展的bad-character规则”:记录pattern中每一个位置i上,字符c先于i出现最后位置,即对于0 ≤ i < m,记录bcTable’[i, c] = max{ j: pattern[j] = c 且 j < i }。对于小的字符集而言,这会对效率起到很大程度的改进,但由于很多实际情况下这个做法反而会导致性能的损失,因此较少采用。
为了更加便于理解,这里给出根据定义求bad-character跳跃表的Python代码:
def bmbc ( p, charset=r'agct' ):
bc = {}
lp = len(p)
for c in charset:
bc[c] = -1
for i in range(lp-1):
bc[ p[i] ] = i
return bc
其中,参数p为模式串,参数charset为字符集(默认为DNA碱基序列agct);返回值为字符集对应的坏字符跳转表。
1.3、Good-suffix跳转原理说明
假设通过自右向左的匹配,已经得到了pattern[ i : m ] = text[ j+i : j+m ] = u,且pattern[ i-1 ] ≠ text[ j+i-1 ],那么可以分两种情况:
情况一,pattern中,在i之前还存在子串u,并且子串u之前的字符不等于pattern[ i-1 ]:
Text
a
u
pattern
b
u
←shift→
c
u
图1.5 u在匹配失败的地方之前重现,并且u前面的字符不为b
不妨定义R(i):R(i) 是能使pattern[ i : m ] 成为pattern [ 0 : R(i) ] 的后缀、且这样一个后缀前的字符不为pattern[ i-1 ] 的最大值;若这样的值不存在,则令R(i) 为 -1。
在图1.5表示的这种情况下,我们可以移动窗口使R(i) 对齐模式串尾部当前所在的地方。
情况二,pattern中,i之前不存在子串u,但pattern的一个前缀v与u的一个后缀相匹配:
text
a
u
pattern
b
u
←
shift
→
v
图1.6 u的后缀v在字符串中重现
不妨定义R'(i):R'(i) 是pattern[ i : m ] 的能成为 pattern 一个前缀的后缀(图示v)的最大长度,若这样的值不存在,则为 -1。
图1.6这种情况下,我们同样可以通过移动窗口使R'(i) 对齐模式串尾部当前所在的地方。
定义这样的good-suffix跳转规则同样是为了避免不必要的比较操作,以模式串gcagagag为例,若末位的g成功匹配了,而倒数第二位的a没有匹配上,那么可以将窗口右移7位:
g
c
a
g
a
g
a
g
g
c
a
g
a
g
a
g
#1
g
c
a
g
a
g
a
g
#2
g
c
a
g
a
g
a
g
#3
g
c
a
g
a
g
a
g
#4
g
c
a
g
a
g
a
g
#5
g
c
a
g
a
g
a
g
#6
g
c
a
g
a
g
a
g
#7
图1.7 仅末位匹配时的good-suffix跳转情况示意
因为,如图1.7所示,若右移一位(情况#1),则位置-2的a注定不匹配;若右移两位(#2),则位置-4的a注定不匹配;依此类推。
同样的道理,若仅有末位的ag得到匹配,那么可以安全的将当前窗口右移4位:
g
c
a
g
a
g
a
g
g
c
a
g
a
g
a
g
#1
g
c
a
g
a
g
a
g
#2
g
c
a
g
a
g
a
g
#3
g
c
a
g
a
g
a
g
#4
g
c
a
g
a
g
a
g
#5
g
c
a
g
a
g
a
g
#6
g
c
a
g
a
g
a
g
#7
图1.8 末两位匹配的good-suffix跳转情况示意
这里,#4和#7都是可能得到正确匹配的情况,因此选择相对较小的跳转,以避免漏过匹配。
为了更便于理解,这里给出根据定义求good-suffix跳跃表的Python代码:
def bmgs ( p ):
lp = len(p)
gs = [lp] * lp
j = lp
while j>0:
ls = lp - j
for i in range(-ls+1, 1): # 情况二
if p[0:ls+i] == p[j-i:lp]:
gs[j-1] = j-i
for i in range(1,j): # 情况一
if p[i:i+ls] == p[j:lp] and p[i-1] != p[j-1] :
gs[j-1] = j-i
j = j-1
return gs
其参数为模式串pattern,返回值为对应的good-suffix跳转表。
1.4、完整的Boyer-Moore查找示例
以在字符串agcatagcatacaagagaagagacagtagagactatta中查找agagacagtag为例,
Bad-character跳转表为:{'a': 9, 'c': 5, 't': 8, 'g': 7}
Good-suffix跳转表为:[9, 9, 9, 9, 9, 9, 9, 9, 3, 11, 1]
查找过程如下图:
a
g
c
a
t
a
g
c
a
t
a
c
a
a
g
a
g
a
a
g
a
g
a
c
a
g
t
a
g
a
g
a
c
t
a
t
t
a
a
g
a
g
a
c
a
g
t
a
g
a
g
a
g
a
c
a
g
t
a
g
a
g
a
g
a
c
a
g
t
a
g
a
g
a
g
a
c
a
g
t
a
g
a
g
a
g
a
c
a
g
t
a
g
a
g
a
g
a
c
a
g
t
a
g
a
g
a
g
a
c
a
g
t
a
g
a
g
a
g
a
c
a
g
t
a
g
图1.9 Boyer-Moore算法运行过程示例
从图1.9中可以看出,在查找过程中,Boyer-Moore算法做了8次尝试,总共22次比较操作;而常规的字串查找算法则会需要 (n - m) m = 297次比较操作,这个差距应该说是非常大的。
使用后面实现的Python代码(http://docs.google.com/leaf?id=0B9sqyhyu5n-UM2ZmMzYxMTYtZDY5YS00ZTE5LWIxMTYtYTcyMmJiY2M3ODQ1&hl=zh_CN),通过调用bm ('agcatagcatacaagagaagagacagtagagactatta', 'agagacagtag') 可以验证这个过程。
- Boyer-Moore 算法详解
- Boyer-Moore算法详解(二)
- 多数投票算法(Boyer-Moore Algorithm)详解
- Boyer-Moore算法学习
- Boyer-Moore算法学习
- Boyer-Moore算法学习
- Boyer-Moore算法学习
- Boyer-Moore算法学习
- Boyer-Moore算法学习
- Boyer-Moore算法学习
- Boyer-Moore算法
- Boyer-Moore算法学习
- Boyer-Moore算法
- Boyer-Moore算法
- Boyer-Moore算法学习
- Boyer-Moore算法学习
- Boyer-Moore算法
- Boyer-Moore算法
- 浅谈3G
- 加载Spring配置文件常用的三种方法
- [摘自互联网]软件需求的层次
- flash 容器滚动效果
- 免费图书下载
- Boyer-Moore 算法详解
- 过滤非法字符
- Ubuntu 库文件链接 搜索当前路径的问题
- [摘自互联网]控制流图及圈复杂度计算
- 转载:Cocoa 学习资源分享
- 搜索引擎根据原Sphider的脚本修正后的 Sphider-plus 2.2
- JAVA字符串转日期或日期转字符串
- Mondrian系列暂时停止更新一周
- sql 递增 update