您的位置:首页 > 其它

Boyer-Moore 算法详解

2010-01-12 11:15 169 查看
写在前面:我的本意是想以通俗的语言来介绍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]

,其时间复杂度可以达到亚线性,而且对于没有周期规律的模式串,最差情况下也只需要3

n
次比较。

约定和术语

约定

字符串和数组的下标均以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、

B
ad-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]
= m
ax
{
i

:

0

i
<

m



pattern
[
i
]=c}
若c
存在于

pattern
中,其他情况为 -1


注意
B
ad-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、

G
ood-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')
可以验证这个过程。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: