算法-字符串匹配(二)
概述
BM 算法是一种经典地字符串匹配算法,而且实践证明:该算法比 KMP 算法匹配性能快 $3$ ~ $5$ 倍,故而文本编辑器的 “查询” 功能通常基于此算法实现。
在 KMP 算法中,文本串 text 与模式串 pattern 中字符采用从前往后方式进行比较。而在 BM 算法中,文本串 text 与模式串 pattern 中字符采用从后往前方式进行比较,其原因在于:1. 当采用从前往后方式比较时,如果第一个字符匹配失败,则 text 仅能前进一步 (即 i = i + 1);当采用从后往前方式比较时,如果最后一个字符匹配失败,则 text 可前进 pattern.length() 步 (即 i = i + pattern.length() ) 。故而采用从后往前方式比较可取得更大的前进步数。2. 采用从后往前方式比较,存在多种事实规则以跳过无效匹配。
接下来,我们将首先给出从后往前比较字符以实施匹配字符串的算法,随后在此基础之上介绍 BM 算法。
基础算法
从后往前比较字符以实现字符串匹配算法,其实现代码如下:
1 | public int match(String text, String pattern) { |
BM 算法
BM 算法对基础算法的修改点就在于:基于某些事实规则尽可能多地移动步数 ( 即i += ? )。
BM 算法中,存在两种事实规则 —— 坏字符规则和好后缀规则 (详细内容可参见 字符串匹配的Boyer-Moore算法)。
当 text 与 pattern 中字符匹配失败时,根据两种规则计算移动步数,随后根据二者大者进行移动,然后继续进行匹配。
坏字符表
坏字符表用于实现坏字符规则,它是一个数组 bc[],其中 bc[i] 具有如下含义:bc[i] 所示字符在 pattern 中最右侧出现位置。
坏字符表实现如下:
1 | private int[] genBc(String pattern) { |
好后缀表
好后缀表用于实现好后缀规则,它由两个数组组成,一个是 suffix[],其中 suffix[k] 具有如下含义:长度为 $k$ 的后缀在 pattern 中出现时其首字母位置下标,一个是 prefix[],其中 prefix[k] 具有如下含义:长度为 $k$ 的后缀是否存在一个相同的前缀。
此时重点就在于:如何根据 pattern 求取 suffix[] 和 prefix[]。求取方式十分巧妙:遍历 pattern 的所有真前缀,随后求取真前缀与 pattern 的公共后缀。在求取公共后缀过程中,我们可以动态得到这两个数组。具体参见如下代码:
1 | private void genGs(String pattern, int[] suffix, boolean[] prefix) { |
匹配过程
BM 匹配过程十分简单,就是根据匹配失败位置及两种规则计算移动步数,随后选择大者进行移动即可。具体实现代码如下:
1 | private int moveByGs(int[] suffix, boolean[] prefix, int j, int length) { |
时空复杂度
理论证明:最坏时间复杂度为 $O(text.length + pattern.length)$,最好时间复杂度为 $O(text.length)$,空间复杂度为 $O(pattern.length)$。
与 KMP 比较
简单说明 KMP 与 BM 匹配算法的异同点:
- 两种算法均基于某些事实规则以跳跃无效匹配。
- 在 KMP 算法中,基于相同前后缀实施跳跃;在 BM 中,基于坏字符规则和好后缀规则实施跳跃,并且好后缀规则中包含了相同前后缀。
- 在 KMP 算法中,从前往后匹配
text与pattern中字符;在 BM 算法中,从后往前匹配text与pattern中字符。 - 在 KMP 算法中,主要移动
pattern;在 BM 算法中,主要移动text。