Boyer-Moore算法,超全解释
Boyer-Moore算法是一种用于字符串匹配的高效算法,由Robert S. Boyer和J Strother Moore于1977年提出。它利用了两个启发式策略:坏字符规则(Bad Character Rule)和好后缀规则(Good Suffix Rule),能够在最坏情况下仅需O(n/m)次比较操作就可以完成匹配过程。本文将详细介绍Boyer-Moore算法的基本思想、实现原理以及相关的优化策略。
Boyer-Moore算法的基本思想
Boyer-Moore算法采用反向搜索的方式,即从模式串的末尾开始,逐个字符地向前匹配。如果当前字符匹配成功,则继续向前匹配;如果匹配失败,则通过坏字符规则和好后缀规则分别计算向右移动的距离,并取两者的最大值进行移动。具体来说,Boyer-Moore算法包含以下两个步骤:
- 预处理:根据模式串中的字符构建两个表格,即坏字符表格和好后缀表格,用于快速计算匹配失败时需要向右移动的距离。
- 匹配:从主串的末尾开始,逐个字符地与模式串的末尾进行匹配。如果当前字符匹配失败,则根据坏字符表格和好后缀表格计算向右移动的距离,并进行移动操作;如果匹配成功,则将模式串向左移动一位继续匹配。
例如,假设我们要在主串中查找模式串“abcde”,其中主串为“abacde”,我们可以先从模式串的末尾开始,也就是从字符“e”开始逐个字符地向前匹配。首先,字符“e”与主串中的字符“e”匹配成功,然后字符“d”与主串中的字符“c”匹配失败,根据坏字符规则和好后缀规则计算出需要向右移动的距离为2,在主串中将模式串向右移动2位,变成“ abcde”。然后,字符“e”与主串中的字符“e”再次匹配成功,依此类推,直到找到第一个匹配的位置。
Boyer-Moore算法的实现原理
Boyer-Moore算法的实现原理主要包含以下两个方面:
- 坏字符规则(Bad Character Rule):对于模式串中的每个字符,计算其在模式串中最右边出现的位置,并将其记录在一个坏字符表格中。当匹配失败时,根据当前主串字符与模式串的匹配情况,在坏字符表格中查询需要向右移动的距离。
- 好后缀规则(Good Suffix Rule):对于模式串中的每个后缀子串,计算其在模式串中匹配成功的最右位置,并将其记录在一个好后缀表格中。当匹配失败时,根据当前模式串字符的匹配情况和好后缀表格中的信息,计算需要向右移动的距离。
例如,假设我们要在主串中查找模式串“abcde”,其中模式串的长度为5。首先,我们可以根据坏字符规则将模式串中的每个字符的最右出现位置记录在一个坏字符表格中:
字符 | a | b | c | d | e |
---|---|---|---|---|---|
位置 | 0 | 1 | 2 | 3 | 4 |
然后,我们可以根据好后缀规则将模式串中的每个后缀子串的最右匹配位置记录在一个好后缀表格中:
后缀子串 | e | de | cde | bcde | abcde |
---|---|---|---|---|---|
位置 | 4 | 3 | 2 | -1 | -1 |
其中“-1”表示该后缀子串没有在模式串中匹配成功。注意到,在好后缀表格中,最后一个后缀子串“abcde”的位置应该为“0”,但当前情况下只有部分匹配成功,因此位置为“-1”。当匹配失败时,我们可以根据当前主串字符与模式串的匹配情况和以上两个表格中的信息,计算需要向右移动的距离。
Boyer-Moore算法的优化策略
为了进一步提高Boyer-Moore算法的运行效率,可以采用以下优化策略:
- 双关键字索引(Double-Keyword Index):在坏字符规则中使用两个相邻的字符位作为关键字,以减少哈希冲突和冲突处理时间。
- 快速跳跃(Quick Skip):在坏字符规则中采用快速跳跃策略,记录所有与上一个坏字符匹配失败的位置,并在下一次匹配失败时直接跳过这些字符,加速向右移动的过程。
- 多模式匹配(Multiple Pattern Matching):将多个模式串合并成一个大的模式串,通过建立双关键字索引、多个坏字符表格和多个好后缀表格等数据结构,扩展Boyer-Moore算法的适用范围。
算法使用
使用Boyer-Moore算法的步骤如下:
-
对模式串进行预处理,生成好后缀规则和坏字符规则。预处理过程可以利用两个数组记录模式串中每个字符最后出现的位置(坏字符规则数组)和从后往前最长的相同后缀及其位置(好后缀规则数组)。
-
在文本串中按照模式串末尾开始匹配,一旦发现不匹配,根据坏字符规则和好后缀规则进行跳跃。
-
如果匹配成功,则找到了模式串在文本串中的位置。
实际使用Boyer-Moore算法时需要注意以下几点:
-
对于短模式串或者文本串,暴力搜索可能更加高效。
-
在实现中,可以使用多个指针来记录匹配的位置和跳跃的位置。
-
预处理过程需要O(m)的时间复杂度,匹配过程的最坏时间复杂度为O(nm),但在大多数情况下实际运行时间会远远小于暴力搜索。
-
由于Boyer-Moore算法的实现比较复杂,可以考虑直接使用现成的库或者工具,如Python中的re模块。
算法代码
下面是 Boyer-Moore 算法的 Python 代码实现:
def boyer_moore(text, pattern):
n = len(text)
m = len(pattern)
# 制作坏字符表
bc = [-1] * 256
for i in range(m):
bc[ord(pattern[i])] = i
# 制作好后缀表
suffix, prefix = get_suffix_prefix(pattern)
gs = [-1] * m
j = 0
for i in range(m-1, -1, -1):
if suffix[i] == i+1:
while j < m-1-i:
if gs[j] == -1:
gs[j] = m-1-i
j += 1
for i in range(m-1):
gs[m-1-suffix[i]] = m-1-i
# 匹配
i = 0
while i <= n-m:
j = m-1
while j >= 0:
if text[i+j] != pattern[j]:
break
j -= 1
if j < 0:
return i
x = j - bc[ord(text[i+j])] if j < m-1 else 1
y = gs[j] if j < m-1 else 0
i += max(x, y)
return -1
def get_suffix_prefix(pattern):
m = len(pattern)
suffix = [0] * m
prefix = [-1] * m
for i in range(m-1):
j = i
k = 0
while j >= 0 and pattern[j] == pattern[m-1-k]:
j -= 1
k += 1
suffix[k] = j+1
if j == -1:
prefix[k] = True
return suffix, prefix
该代码中的 boyer_moore
函数接受两个字符串 text
和 pattern
,并返回 pattern
在 text
中第一次出现的位置,如果没有出现则返回 -1。
boyer_moore
函数首先制作了坏字符表和好后缀表。然后利用这两个表进行匹配。
匹配时,从 text
的头部开始,依次与 pattern
的每个字符进行比较。如果发生不匹配,则根据坏字符表和好后缀表计算出移动距离。移动距离取坏字符表和好后缀表中计算出的距离的最大值。移动到下一个位置后,继续匹配。
在匹配过程中,如果找到了一个完全匹配的位置,则返回该位置的下标。
get_suffix_prefix
函数用于制作好后缀表。这个函数用到了两个数组 suffix
和 prefix
。数组 suffix
记录了每个后缀的最长匹配前缀的长度。数组 prefix
记录了每个前缀是否是 pattern
的后缀。
总结
Boyer-Moore算法是一种高效的字符串匹配算法,采用反向搜索的方式,在最坏情况下仅需O(n/m)次比较操作就可以完成匹配过程。Boyer-Moore算法包含两个步骤:预处理和匹配。在实际应用中,可以通过双关键字索引、快速跳跃、多模式匹配等优化策略进一步提高Boyer-Moore算法的运行效率。