字符串匹配算法——BM (Boyer-Moore) 算法
概述
字符串匹配算法:在一个大的字符串T中搜索某个字符串P的所有出现位置,例如字符串T为abcdef,模式串(搜索词)P为bc。通过字符串匹配算法返回的位置为1。
常见的算法包含:BF算法(Brute Force 暴力算法)、RK算法(Rabin-Karp 哈希匹配算法)、BM算法(Boyer-Moore 坏字符和好后缀算法)、KMP算法(最长可匹配前后缀子串算法),本文主要介绍Bm算法。
Bm算法整体流程大致分为3步。
1. 从后向前匹配逐个字符:首先,从后向前逐个匹配字符,如果字符不相同则称为坏字符,如果字符相同则为好后缀。然后再根据好后缀和坏字符计算模式串(搜索词)的移动位置。
2. 如果模式串(搜索词)P没有完全匹配字符串T。根据好后缀和坏字符的位置计算模式串(搜索词)向后移动的位置。
2.1 坏字符计算方式:移动位数=坏字符在模式串的位置 - 模式串上一次的位置
2.2 好后缀计算方式:移动位数=好后缀在模式串的位置 - 模式串上一次的位置
3. 重复上述步骤,如果模式串匹配到字符串则返回对应的位置,如果没有能匹配的字符则返回 -1。
本文通过5个场景说明BM(Boyer-Moore) 算法的执行步骤。
动画算法总体概览图: 动画算法总体概览图
文章中使用的动画网站地址,限 pc:
场景一:坏字符场景且模式串中没有匹配字符 算法动画
http://www.donghuasuanfa.com/platform/portal?pc=boyer-moore
场景二:坏字符场景且模式串中有匹配字符 算法动画
http://www.donghuasuanfa.com/platform/portal?pc=boyer-moore-case2
场景三:好后缀场景且模式串中没有匹配字符
http://www.donghuasuanfa.com/platform/portal?pc=boyer-moore-case3
场景四:好后缀场景且模式串中有匹配字符
http://www.donghuasuanfa.com/platform/portal?pc=boyer-moore-case4
场景五:好后缀场景且模式串中有匹配子串后缀字符
http://www.donghuasuanfa.com/platform/portal?pc=boyer-moore-case5
场景一 坏字符场景且模式串中没有匹配字符
首先从后往前逐个匹配字符,因为字符串的k和模式串(搜索词)的d不相符,所以认为k是坏字符,因此无需再比较剩余字符。
然后计算模式串(搜索词)向后移动多少位置,位置计算公式为:移动位数=坏字符在模式串的位置 - 模式串上一次的位置。
坏字符在模式串的位置:示例中k为坏字符,所以对应的模式串的位置为3。
模式串上一次的位置:坏字符k寻找位置的过程为逐个比较模式串之前的字符,如果匹配则返回对应的位置,如果不匹配则返回-1。当前场景为没有能匹配坏字符的场景,所以上一个位置为-1。
因此移动位置的计算结果为 3-(-1)=4。整体过程如下图1-1所示。
接下来移动模式串,移动的位置为4,过程入如下图1-2所示。
场景二 坏字符场景且模式串中有匹配字符
场景二也是坏字符场景。首先从后往前逐个匹配字符,因为字符串的k和模式串(搜索词)的d不相符,所以认为k是坏字符,因此无需再比较剩余字符。
然后计算模式串(搜索词)向后移动多少位置,位置计算公式为:移动位数=坏字符在模式串的位置 - 模式串上一次的位置。
坏字符在模式串的位置:示例中k为坏字符,所以对应的模式串的位置为3。
模式串上一次的位置:坏字符k寻找位置的过程为逐个比较模式串之前的字符,如果匹配则返回对应的位置,如果不匹配则返回-1。当前场景为有能匹配坏字符的场景,除了坏字符外,模式串上一个坏字符的位置为1,所以上一个位置为1。
因此移动位置的计算结果为 3-1 =2。整体过程如下图2-1所示。
接下来移动模式串,移动的位置为2,过程入如下图2-2所示。
场景三 好后缀场景且模式串中没有匹配字符
场景三为好后缀场景。首先从后往前逐个匹配字符,因为字符串的kd和模式串(搜索词)的kd相符,所以认为kd是好后缀。同时因为字符串的c和模式串(搜索词)的t不相符,所以认为c是坏字符。因此无需再比较剩余字符。
寻找好后缀和坏字符如下图3-1所示。
然后计算模式串(搜索词)向后移动多少位置,如果字符串同时包含好后缀和坏字符则需要分别计算需要移动的位置。计算后选择移动位置多的进行移动。
坏字符位置计算公式为:移动位数=坏字符在模式串的位置 - 模式串上一次的位置。
好后缀位置计算公式为:移动位数=好后缀在模式串的位置 - 模式串上一次的位置 。
根据好后缀计算移动的位置。好后缀kd对应的模式串的位置为最右侧字符的位置,最右侧字符为d,所以位置为4。然后再寻找kd在模式串上一次的位置,由于模式串前三个字符nut都不包含kd,所以好后缀在模式串上一次的位置为-1,因此计算出移动位置为5。整体过程如下图3-2所示。
好后缀计算移动的位置除了计算好后缀字符串还需要计算好后缀的子串,子串即逐个去掉好后缀的左侧字符。示例中kd去掉左侧的字符k后保留的子串只有一个d,并且d所对应的模式串的位置依然为4。子串比较规则为只需要比较模式串的头部即可,无需逐个比较模式串的字符。因为子串d与模式串首个字符n不相等,所以好后缀在模式串上一次的位置为-1,因此计算出移动位置为5。整体过程如下图3-3所示。
继续计算坏字符场景,坏字符场景计算过程和场景1、2一致,此处不再赘述,经计算后坏字符的移动位置为3。整体过程如下图3-4所示。
当同时包含好后缀和坏字符时,则选择移动位置最大的进行移动,因为好后缀移动位置为5,坏字符移动位置为3.所以模式串整体移动5的位置。整体过程如下图3-5 所示。
场景四 好后缀场景且模式串中有匹配字符
场景四也为好后缀场景。首先从后往前逐个匹配字符,因为字符串的ct和模式串(搜索词)的ct相符,所以认为ct是好后缀。同时因为字符串的m和模式串(搜索词)的k不相符,所以认为m是坏字符。因此无需再比较剩余字符。整体过程如下图4-1所示。
然后计算模式串(搜索词)向后移动多少位置,如果字符串同时包含好后缀和坏字符则需要分别计算需要移动的位置。计算后选择移动位置多的进行移动。
坏字符位置计算公式为:移动位数=坏字符在模式串的位置 - 模式串上一次的位置。
好后缀位置计算公式为:移动位数=好后缀在模式串的位置 - 模式串上一次的位置 。
当前场景为好后缀在模式串前有匹配字符,
好后缀ct对应的模式串的位置通常为最右侧字符的位置,最右侧字符为t,所以位置为8。然后再寻找ct在模式串上一次的位置,由于模式串第4、5个字符为ct,所以好后缀在模式串上一次的位置为5,因此计算出移动位置为3。
好后缀在模式串能够匹配成功后如果再按照子串移动会发生过度移动,所以无需再比较计算好后缀的t子串的移动位置。
整体计算过程如下图4-2 所示。
继续计算坏字符场景,坏字符场景计算过程和场景1、2一致,此处不再赘述,经计算后坏字符的移动位置为3。整体过程如下图4-3所示。
当同时包含好后缀和坏字符时,则选择移动位置最大的进行移动,示例中好后缀和坏字符计算结果都为3,所以移动的位置为3。整体过程如下图4-4 所示。
因为好后缀在模式串匹配成功的移动位置为最优的移动位置。所以无需再计算好后缀的子串和坏字符的移动位置,读者如果有疑议可自行找例子计算。
场景五 好后缀场景且模式串中有匹配子串后缀字符
场景五也为好后缀场景。首先从后往前逐个匹配字符,因为字符串的kct和模式串(搜索词)的kct相符,所以认为kct是好后缀。同时因为字符串的m和模式串(搜索词)的s不相符,所以认为m是坏字符。因此无需再比较剩余字符。整体过程如下图5-1所示。
根据好后缀计算移动的位置。好后缀kct对应的模式串的位置为最右侧字符的位置,最右侧字符为t,所以位置为5。然后再寻找kct在模式串上一次的位置,由于模式串前三个字符tms都不包含kd,所以好后缀kct在模式串上一次的位置为-1,因此计算出移动位置为6。整体过程如下图5-2所示。
好后缀计算移动的位置除了计算好后缀字符串还需要计算好后缀的子串,子串即逐个去掉好后缀的左侧字符。示例中kct依次去掉左侧的字符后保留的子串为ct、t。ct、t这俩个子串在模式串的位置以最右侧字符为准,所以位置为5。
ct子串与模式串前俩个字母tm比较,因为不相符,所以子串ct的上一个位置为-1,因此ct子串计算移动位置为6。
ct子串计算过程如下图5-3所示。
t子串与模式串首个字母t比较,因为相符,所以子串t的上一个位置为0,因此t子串计算移动位置为5。因为好后缀有多个计算结果,所以多个好后缀之间的移动位置取最短的长度,以防止过度移动。所以当前示例好后缀的移动距离为5。
t子串计算过程如下图5-4所示。
继续计算坏字符场景,坏字符场景计算过程和场景1、2一致,此处不再赘述,经计算后坏字符的移动位置为3。整体过程如下图5-5所示。
当同时包含好后缀和坏字符时,则选择移动位置最大的进行移动,示例中好后缀的计算结果为5,坏字符计算结果为1,所以移动的位置为5。整体过程如下图5-6 所示。
所有示例结束。
总结
Bm算法整体流程大致分为3步。
1. 从后向前匹配逐个字符:首先,从后向前逐个匹配字符,如果字符不相同则称为坏字符,如果字符相同则为好后缀。然后再根据好后缀和坏字符计算模式串(搜索词)的移动位置。
2. 如果模式串(搜索词)P没有完全匹配字符串T。根据好后缀和坏字符的位置计算模式串(搜索词)向后移动的位置。
2.1 坏字符计算方式:移动位数=坏字符在模式串的位置 - 模式串上一次的位置
2.2 好后缀计算方式:移动位数=好后缀在模式串的位置 - 模式串上一次的位置
3. 重复上述步骤,如果模式串匹配到字符串则返回对应的位置,如果没有能匹配的字符则返回 -1。