【重学数据结构与算法(JS)】字符串匹配算法(三)——BM算法

前言

文章的一开头,还是要强调下字符串匹配的思路:

  1. 模式串主串进行比较

    • 从前往后比较
    • 从后往前比较
  2. 匹配时,比较主串模式串的下一个位置
  3. 失配时,

    • 模式串中寻找一个合适的位置

      • 如果找到,从这个位置开始与主串当前失配位置进行比较
      • 如果未找到,从模式串的头部与主串失配位置的下一个位置进行比较
    • 主串中找到一个合适的位置,重新与模式串进行比较

前面的 BFKMP 算法,都是属于规规矩矩从前向后的操作,后者仅在寻找模式串的合适位置上进行了优化,而 BM 算法的操作就显得骚了很多,它的优化点在于:

  1. 从后往前比较
  2. 失配时,寻找的是主串中合适的位置

算法介绍与分析

关于算法的介绍和分析,网上有很多解释,这里推荐一下阮一峰的字符串匹配的Boyer-Moore算法,很清楚的讲解了整个优化的思路,可以先看完理解了再往下看,因为下面主要介绍一下坏字符规则好后缀规则需要的数据结构的手工求法以及代码实现。

坏字符规则

运用坏字符规则,在算法里主要体现在生成一张散列表,表的key值是字符集里每个字符的ASCII码值,value值是模式串中该字符的位置,举个栗子:

QQ20200119-202854.png

假设字符串的字符集不是很大,用长度为256的数组来存储,并且初值赋值为-1。数组的下标对应字符的 ASCII 码值,数组中存储这个字符在模式串中出现的位置。这里要特别说明一点,如果坏字符在模式串里多处出现,选择最靠后的那个,因为这样不会让模式串滑动过多,导致本来可能匹配的情况被滑动略过。

好后缀规则

好后缀规则体现在如何求出 suffixprefix 两个数组以及移动规则

suffix 数组

key值表示的是后缀子串的长度,value值表示的是在模式串中跟好后缀 S 相匹配的最后一个子串 S' 的首字母在模式串中的key值,如下图:

QQ20200119-211057.png

prefix 数组

同样的,key值表示的是后缀子串的长度,而value值表示的是模式串中,是否有和该长度下后缀子串相同的前缀子串,是的话为 true,否则为 false,如下图:

QQ20200119-212509.png

移动规则

移动规则总结如下:

  • 模式串中寻找跟好后缀 S 相匹配的最后一个子串 S'

    • 如果找到,将模式串移动到使得 S'主串对齐的位置
    • 如果找不到,再寻找模式串前缀子串中是否有和 好后缀 S后缀子串匹配的位置,滑动模式串以对齐。
    • 如果仍然找不到,则将模式串移动至主串模式串末尾对齐的下一个位置

下图分别对应三种情况:

QQ20200119-215102.png
QQ20200119-220640.png
QQ20200119-221219.png

代码实现

整体逻辑框架

参考字符串匹配的思路

  • 仍然需要进行主串模式串的字符对比,所以需要两个指针 ij 分别指向主串模式串,记录位置
  • 需要一个循环来重复进行匹配操作,此时思考终止条件:

    • i 指向主串每次匹配的合适位置,从前往后扫描;j 指向模式串的尾部,从后往前扫描。考虑极端情况:主串模式串对比完,仍然无法匹配。此时,i 的位置一定小于等于 主串长度 n模式串长度 m 的差值。具体可看下图。
  • 每次模式串从后往前与主串进行匹配,这也需要一个内层循环来驱动指针j
  • 如果匹配,只需要继续移动匹配位置即可
  • 如果失配,分别根据坏字符规则好后缀规则计算出 i 需要移动的位置,选择两个值当中最大的,重新计算 i 的值,重复进行匹配。

QQ20200120-202236.png

根据以上分析可以写出整个的逻辑框架代码:

carbon.png

框架写好后,接下来就是完善三个辅助函数即可

求坏字符散列表

这个就没有什么可以多说的了,只要参考上面分析的,一步一步写出代即可:

carbon的副本.png

求好后缀记录数组 suffixprefix

拿下标从 0i 的子串(i 可以是 0m-2)与整个模式串,求公共后缀子串。如果公共后缀子串的长度是 k,那就记录 suffix[k]=jj 表示公共后缀子串的起始下标)。如果 j 等于 0,也就是说,公共后缀子串也是模式串的前缀子串,就记录 prefix[k]=true。可以自己动下手,模拟下代码的运行,尤其注意中k值的运用,很巧妙。

carbon的副本2.png

求好后缀移动步数

根据上面此步的算法分析,也可以写出:

carbon的副本3.png

总结

总的来说,BM算法另辟蹊径,通过从后往前的匹配的思路,加上坏字符规则好后缀规则来优化移动的步数,从而提高算法的匹配效率。

后记

“字符串匹配算法”是“重学数据结构与算法”系列笔记:


前端的尤里卡时刻
一些基础巩固 一些原理解析 一些最佳实践 一些踩坑经历
113 声望
5 粉丝
0 条评论
推荐阅读
关于用设计模式刷 LeetCode 这件事
最近在过 《剑指Offer》 这本书上的题,尽量把每题的多种解法都自己捋一遍,在过到 面试题20. 表示数值的字符串 这一题的时候,Discuss 里有一个同学提出了 职责链模式 的解法,让人眼前一亮,另一方面是笔者最近...

LazyDuke3阅读 982

JavaScript有用的代码片段和trick
平时工作过程中可以用到的实用代码集棉。判断对象否为空 {代码...} 浮点数取整 {代码...} 注意:前三种方法只适用于32个位整数,对于负数的处理上和Math.floor是不同的。 {代码...} 生成6位数字验证码 {代码...} ...

jenemy49阅读 7.4k评论 12

再也不学AJAX了!(二)使用AJAX ① XMLHttpRequest
「再也不学 AJAX 了」是一个以 AJAX 为主题的系列文章,希望读者通过阅读本系列文章,能够对 AJAX 技术有更加深入的认识和理解,从此能够再也不用专门学习 AJAX。本篇文章为该系列的第二篇,最近更新于 2023 年 1...

libinfs42阅读 7k评论 12

封面图
「多图预警」完美实现一个@功能
一天产品大大向 boss 汇报完研发成果和产品业绩产出,若有所思的走出来,劲直向我走过来,嘴角微微上扬。产品大大:boss 对我们的研发成果挺满意的,balabala...(内心 OS:不听,讲重点)产品大大:咱们的客服 I...

wuwhs32阅读 3.6k评论 5

封面图
安全地在前后端之间传输数据 - 「3」真的安全吗?
在「2」注册和登录示例中,我们通过非对称加密算法实现了浏览器和 Web 服务器之间的安全传输。看起来一切都很美好,但是危险就在哪里,有些人发现了,有些人嗅到了,更多人却浑然不知。就像是给门上了把好锁,还...

边城29阅读 6.4k评论 5

封面图
2022大前端总结和2023就业分析
我在年前给掘金平台分享了《2022年热点技术盘点》的前端热点,算是系统性的梳理了一下我自己对前端一整年的总结。年后,在知乎上看到《前端的就业行情怎么样?》,下面都是各种唱衰前端的论调,什么裁员,外包化...

i5ting27阅读 2.4k评论 4

封面图
深入理解React Diff算法
fiber上的updateQueue经过React的一番计算之后,这个fiber已经有了新的状态,也就是state,对于类组件来说,state是在render函数里被使用的,既然已经得到了新的state,那么当务之急是执行一次render,得到持有新...

nero31阅读 11.8k评论 3

113 声望
5 粉丝
宣传栏