BM算法
描述
BM算法即Boyer-Moore算法,是由Bob Boyer和J Strother Moore于1977年提出的,是一种非常高效的字符串搜索算法。
BM算法从模式串的尾部开始匹配,最坏情况下时间复杂度为O(n*m),n为文本串长度,m为模式串长度。
the Boyer-Moore-Galil和the Turbo-BM两个算法做了进一步优化,最坏情况下达到线性。
可参考论文:
《On improving the worst case running time of the Boyer-Moore string searching algorithm》
BM算法主要定义了两个规则:坏字符规则和好字符规则。
规则
对于每一次模式串的移动,执行以下规则:
从模式串的最后一位字符与文本串相应位置字符开始比对;
相等则获得一个好后缀,并再比对前一个字符;
不相等则按坏字符规则获取模式串移动位数;
存在好后缀时,执行好后缀规则,获取模式串的移动位数;
比对通过坏字符规则和好后缀规则获得的关于模式串移动位数,取最大的值来移动。
坏字符(bad character)规则:
坏字符即文本串中不匹配的字符
模式串移动位数 = 此时坏字符在模式串中的位置 - 模式串中最近一个坏字符的位置(没有时记为-1)
如:文本串为“xxhel”,模式串为“hel”,(不考虑好后缀)
第一次比对如下:
xxxhel
hel
比对“x”和“l”,不相等,得到坏字符“x”。
此时坏字符在模式串中的位置为2,最近一个坏字符的位置为-1
移动位数 = 2 - (-1) = 3 结果如下:
xxxhel
hel
还如:文本串为“xhel”,模式串为“hhl”,(不考虑好后缀)
第一次比对如下:
xxhel
hhl
比对“h”和“l”,不相等,得到坏字符“h”。
此时坏字符在模式串中的位置为2,最近一个坏字符的位置为1
移动位数 = 2 - 1 = 1 结果如下:
xxhel
hhl
好后缀(good suffix)规则:
好后缀即文本串中和模式串匹配得后缀,好后缀的位置为其最后一个字符的位置。
好后缀规则的移动位数,需要考虑两种情况,在比对字符前的模式串中是否存在字符串与最长好后缀相匹配。
- 存在
- 移动位数 = 好后缀在模式串中的位置 - 模式串中最近一个最长好后缀的位置
如:文本串为“xxxxheahebhe”,模式串为“ahebhe”
第一次比对如下:
xxxxheahebhe
ahebhe
比对“e”和“e”,相同,比对字符往前移;
比对“h”和“h”,相同,比对字符往前移;
比对“x”和“b”,不相同。
得到好后缀有“he”,“e”,最大好后缀为“he”
此时好后缀在模式串中的位置为5,比对位置前的最近一个最长好后缀的位置为2
移动位数 = 5 - 2 = 3 结果如下:
xxxxheahebhe
ahebhe
- 不存在
移动位数 = 好后缀在模式串中的位置 - 其他好后缀在模式串中对应前缀位置的最大值(都没有则取-1)
如:文本串为“xxxbhebhe”,模式串为“hebhe”
第一次比对如下:
xxbhebhe
hebhe
比对“e”和“e”,相同,比对字符往前移;
比对“h”和“h”,相同,比对字符往前移;
比对“b”和“b”,相同,比对字符往前移;
比对“x”和“e”,不相同。
得到好后缀有“bhe”,“he”,“e”,最大好后缀为“bhe”
此时好后缀在模式串中的位置位置为4。比对位置前的模式串中不存在最长好后缀,但是存在“he”前缀,位置为1
移动位数 = 4 - 1 = 3 结果如下:
xxbhebhe
hebhe
例子
假设:
要检索的文本串记为T,模式串记为P
文本串T为 “hellooo fish fjfish hshfish”,文本串长度为26。
模式串P为 “hshfish”,模式串的长度为7,下标从0开始,
模式串P中未有的字符在P中的下标记为-1,如“hello”中“i”的下标为-1
查找过程如下:
hellooo fish fjfish hshfish
hshfish
为了方便观察,文本串和模式串写作如下,字符间以 “|”分隔:
0 |1 |2 |3 |4 |5 |6 |7 |8 |9 |10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26
h |e |l |l |o |o |o | |f |i |s |h | |f |j |f |i |s |h | |h |s |h |f |i |s |h
h |s |h |f |i |s |h
0 |1 |2 |3 |4 |5 |6
第一次P的“h”和T的“o”不匹配,“o”为坏字符,在P中并为出现“o”,所以“o”在P中最近一次位置为-1,根据坏字符规则
P右移位数: 6 - (-1) = 7,6为“h”在P中的下标,-1为o在P中的下标
P在T中对应位置: 0 + 7 = 7, 0为P对应T的启示位置,对应字符为“ ”
0 |1 |2 |3 |4 |5 |6 |7 |8 |9 |10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26
h |e |l |l |o |o |o | |f |i |s |h | |f |j |f |i |s |h | |h |s |h |f |i |s |h
h |s |h |f |i |s |h
0 |1 |2 |3 |4 |5 |6
“f”为坏字符,根据规则找到“f”在P中最近一次位置,为3,移动规则:
P右移位数: 6 - 3 = 3
P在T中对应位置: 7 + 3 = 10
0 |1 |2 |3 |4 |5 |6 |7 |8 |9 |10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26
h |e |l |l |o |o |o | |f |i |s |h | |f |j |f |i |s |h | |h |s |h |f |i |s |h
h |s |h |f |i |s |h
0 |1 |2 |3 |4 |5 |6
“i”为坏字符,根据坏字符规则:
P右移位数: 6 - 4 = 2
P在T中对应位置: 10 + 2 = 12
0 |1 |2 |3 |4 |5 |6 |7 |8 |9 |10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26
h |e |l |l |o |o |o | |f |i |s |h | |f |j |f |i |s |h | |h |s |h |f |i |s |h
h |s |h |f |i |s |h
0 |1 |2 |3 |4 |5 |6
得到坏字符“j”(T中14号下标),对应P中2号下标;得到好后缀“h”,“sh”,“ish”,“fish”,最长好后缀为“fish”
根据话字符规则:
P右移位数: 2 - (-1) = 3
P在T中对应位置: 12 + 3 = 15
好后缀位置为6,最长好后缀在P中最近位置为-1(P中无最近位置)。后缀“ish”和“sh”在P中位置为-1(P中不存在相应前缀),只有“h”在P中有相应前缀,位置为0
根据好后缀规则:
P右移位数: 6 - 0 = 6,
P在T中对应位置: 12 + 6 = 18
比较发现好后缀规则移动较大,选择好后缀规则移动
0 |1 |2 |3 |4 |5 |6 |7 |8 |9 |10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26
h |e |l |l |o |o |o | |f |i |s |h | |f |j |f |i |s |h | |h |s |h |f |i |s |h
h |s |h |f |i |s |h
0 |1 |2 |3 |4 |5 |6
得到坏字符“i”(T中下标24,对应P中下标为6),在P中最近的“i”下标为4。
根据坏字符规则:
P右移位数: 6 - 4 = 2
P在T中对应位置: 18 + 2 = 20
0 |1 |2 |3 |4 |5 |6 |7 |8 |9 |10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26
h |e |l |l |o |o |o | |f |i |s |h | |f |j |f |i |s |h | |h |s |h |f |i |s |h
h |s |h |f |i |s |h
0 |1 |2 |3 |4 |5 |6
匹配完成!