一看就懂的字符串匹配算法 之 BM算法
先来一个导读,关于BM算法开始有了难度,大家一定要静下心,咬着呀也得给我读下去,这样你才能有收获。
我们在文本编辑器中,我们经常用到查找及替换功能。比如说,在Word文件中,通过查找及替换功能,可以把某一个单词统一替换成另一个单词。对于文本编辑器这种软件来说,查找及替换是其核心功能,我们希望使用的字符串匹配算法尽可能地高效。之前讨论过RK算法,时间复杂度为O(n),其实已经很高效了,现在来介绍一个新的字符串匹配算法,BM(Boyer-Moore)算法。
BM算法的核心思想
BM算法是一个非常高效的字符串匹配算法,不过BM算法的原理非常复杂,理解起来可能有点困难,大家花费半小时静下心去好好研究一下。
模式串和主串的匹配过程可以看成模式串在主串中不停的向后滑动。当遇到不匹配的字符时,BF算法和RK算法是将模式串往后滑动一位,然后从模式串的第一个字符开始重新匹配。
主串中的字符c在模式串中没有对应的字符,就肯定无法匹配。因此,我们可以一次性把模式串往后多滑动几位,把模式串移动到字符c的后面,如下图。
其实,BM算法本质上就是在寻找某种规律,借助这种规律,在模式串与主串匹配的时候,当模式串与主串中的某个字符不匹配时,能够跳过一些肯定不会匹配的情况,将模式串往后多滑动几位。字符串匹配的效率因此就高了。
BM算法的原理分析
BM算法的具体实现原理包含两个部分:坏字符规则(bad character rule)和好后缀规则(good suffix rule)。
1.坏字符规则
我们知道在BF算法中的模式串和主串之间的匹配是按照下标从小到大的顺序进行的,而BM算法的匹配顺序却是比较特殊,他是按照模式串下标从大到小的顺序倒叙进行的。如下图。
从模式串的末尾往前倒着匹配,当发现某个字符无法匹配的时候,我们就把这个无法匹配的字符称为“坏”字符。注意,坏字符指的是主串中的字符,而不是模式串中的字符。如下图。
我们用坏字符c在模式串中查找,发现模式串中并不存在这个字符,也就是说,字符c与模式串中的任何字符都不可能匹配。这个时候,我们就可以将模式串直接滑动到字符c的后面,再重新从模式串的末尾字符开始比较。
我们将模式串滑动到c之后,就会发现,模式串中的最后一个字符d,还是无法与主串中的字符a相互匹配。此时,我们是否能将模式串滑动到主串中坏字符a(主串中第三个a)的后面?
其实是不可以的,因为坏字符a在模式串中存在,也就是下标为0的位置存储的就是字符a。因此,我们可以将模式串往后滑动两位,让模式串中的a与主串中的第三个a上下对齐,然后再从模式串的末尾字符开始匹配。
现在大家可能会发现一个问题,那就是,第一次我们移动模式串的时候,是移动了三位,但是第二次的时候就是移动了两位,那么对于移动的具体次数,有没有规律呢?
当模式串与主串不匹配时,我们把坏字符对应的模式串中的字符在模式串中的下标记作为si。如果坏字符在模式串中存在,那么我们把坏字符在模式串中的下标记作xi,如果坏字符在模式串中不存在,那么我们把xi记作-1。那么,模式串往后滑动的位数就等于si-xi。
这里还要说明的是,如果坏字符在模式串中出现多次,那么在计算xi的时候,我们选择模式串中最靠后的哪个坏字符的下标作为xi的值。这样就不会因为模式窜滑动过多,而导致本来可能匹配的情况被忽略。
利用坏字符规则,BM算法在最好情况下的时间复杂度非常底,是O(n/m)。例如,主串是aaabaaabaaabaaab,模式串是aaaa,每当模式串与主串不匹配时(坏字符是b),我们就可以将模式串直接往后滑动4位,因此,匹配具有类似特点的主串与模式串的时候,BM算法是高效的。
不过,单纯使用坏字符规则还不够,因为根据si-xi计算出来的滑动位数又可能是负数,如主串是aaaaaaaaaaaa,模式串为baaa。针对这种情况,就还需要另外一种规则,好后缀规则。
2.好后缀规则
好后缀规则与坏字符规则非常类似,当模式串滑动到如下图所示的位置时,模式串与主串有两个字符是匹配的,倒数第三个字符不匹配。
先来看看好后缀规则怎么工作的。
我们把已经匹配的"bc"称为好后缀,记作{u}.我们用他在模式串中进行查找,如果找到另一个与好后缀{u}匹配的子串{u*},那么我们就将模式串滑动到子串{u*}与好后缀{u}上下对齐的位置。如下图。
如果在模式串中找不到好后缀{u}匹配的另外的子串,就直接将模式串滑动到好后缀{u}的后面。
不过大家想没想过这个问题,当模式串中不存在与好后缀{u}匹配的子串时,我们直接将模式串滑动到好后缀{u}后面,是不是有点过头,看下图一个例子,其中"bc"是好后缀,尽管在模式串中没有另外一个与好后缀匹配的子串,但是我们如果模式串移动到好后缀的后面,就会错过模式串和主串可以匹配的情况。
BM算法的代码实现
哈希表的代码构建过程
private static final int SIZE = 256;//全局变量或成员变量//b为模式串,m为模式串长度,bc为哈希表
pivate void generateBC(char[] b,int m,int[] bc){for(int i = 0;i < SIZE;++i){bc[i] = -1; //初始化bc}for(int i = 0;i < m;++i){int ascii = (int)b[i];//计算b[i]的ASCII值bc[ascii] = i; }
}
public int bm(char[] a,int n,char[] b,int m){int[] bc = new int[SIZE];//记录模式串中每个字符出现的位置generateBC(b,m,bc);//构建坏字符哈希表int i = 0;//i表示主串与模式串上下对齐的第一个字符while(i < n-m){int j;for(j = m - 1;j >= 0;--j){ //模式串从后往前匹配if(a[i+j] != b[j]) break; //坏字符对应的模式串中的下标是j}if(j < 0){return i; //匹配成功,返回主串与模式串第一个匹配的字符的位置}//这里将等同于模式串往后滑动j-bc[(int)a[i+j]]位i = i + (j - bc[(int)a[i+j]]);}return -1;
}
BM算法代码实现中的关键变量。
大家载坚持一下看完好后缀规则的实现
我们把 suffix 数组和 prefix 数组的计算过程,用代码实现出来,就是下面这个样子:
void generateGS(char[] b,int m,int[] suffix,boolean[] prefix){for(int i = 0;i < m;++i){//初始化suffix,prefix数组suffix[i] = -1;prefix[i] = false;}for(int i = 0;i < m - 1;++i){//循环处理b[0,i]int j = i;intk = 0;//公共后缀子串的长度while(j >= 0 && b[j] == b[m-1-k]){//与b[0,m-1]求公共后缀子串--j;++k;suffix[k] = j+1;//j+1表示公共后缀子串在b[0,i]中的起始下标}if(j == -1) prefix[k] = true; //公共后缀子串也是模式串的前缀子串}
}
//a和b分别表示主串和模式串,n和m分别表示主串和模式串的长度
public int bm(char[] a,int n,char[] b,int m){int[] bc = new int[SIZE];//记录模式串中每个字符最后出现的位置generateBC(b,m,bc);//构建坏字符哈希表int[] suffix = new int[m];boolean[] prefix = new boolean[m];generateGS(b,m,suffix,prefix);int i = 0;//表示主串与模式串匹配的第一个字符while(i <= n - m){int j;for(j = m - 1;j >= 0;--j){//模式串从后往前匹配if(a[i+j] != b[j]) break; //坏字符对应模式串中的下标是j}if(j < 0){return i; //匹配成功,返回主串与模式串第一个匹配的字符的位置}int x = j - bc[(int)a[i+j]];int y = 0;if(j < m-1){y = moveByGS(j,m,suffix,prefix);}i = i + Math.max(x,y);}return -1;
}//j表示坏字符对应的模式串中的字符下标,m表示模式串长度
private int moveByGs(int j,int m,int[] suffix,boolean[] prefix){int k = m - 1 - j;//好后缀的长度if(suffix[k] != -1) return j - suffix[k] + 1;for(int r = j+2;r <= m-1;++r){if(prefix[m-r] == true){return r;}}return m;
}
BM 算法的性能分析及优化
引用《数据结构与算法之美》
一看就懂的字符串匹配算法 之 BM算法相关推荐
- 一看就懂的字符串匹配算法 之 RK算法
RK算法是对BF算法的进一步优化,很巧妙的使用了哈希算法,让匹配的效率有了很大的提升. BF算法 这是关于BF暴力匹配算法的博客,大家可以先去看看. RK算法的原理和实现 之前在讨论BF算法的时候, ...
- diff算法阮一峰_【重学数据结构与算法(JS)】字符串匹配算法(三)——BM算法
前言 文章的一开头,还是要强调下字符串匹配的思路 将模式串和主串进行比较 从前往后比较 从后往前比较 2. 匹配时,比较主串和模式串的下一个位置 3. 失配时, 在模式串中寻找一个合适的位置 如果找到 ...
- 大量的数据做字符串匹配_【重学数据结构与算法(JS)】字符串匹配算法(三)——BM算法...
前言 文章的一开头,还是要强调下字符串匹配的思路 将模式串和主串进行比较 从前往后比较 从后往前比较 2. 匹配时,比较主串和模式串的下一个位置 3. 失配时, 在模式串中寻找一个合适的位置 如果找到 ...
- 字符串匹配算法之BM算法
BM算法,全称是Boyer-Moore算法,1977年,德克萨斯大学的Robert S. Boyer教授和J Strother Moore教授发明了一种新的字符串匹配算法. BM算法定义了两个规则: ...
- 字符串匹配算法:Horspool算法
Horspool 字符串匹配算法对Boyer-Moore算法的简化算法. Horspool 算法是一种基于后缀匹配的方法,是一种"跳跃式"匹配算法,具有sub-linear亚线性时 ...
- 字符串匹配算法:Sunday算法
背景 我们第一次接触字符串匹配,想到的肯定是直接用2个循环来遍历,这样代码虽然简单,但时间复杂度却是\(Ω(m*n)\),也就是达到了字符串匹配效率的下限.于是后来人经过研究,构造出了著名的KMP算法 ...
- 字符串处理 —— 单模式匹配 —— 朴素的字符串匹配算法(BF 算法)
[算法流程] 朴素的字符串匹配算法即暴力匹配算法(BF,Brute Force),其本质是暴力枚举,主要特点有: 没有预处理阶段: 滑动窗口总是后移 1 位: 对模式中的字符的比较顺序不限定,可以从前 ...
- 字符串匹配算法之Sunday算法
字符串匹配查找算法中,最着名的两个是KMP算法(Knuth-Morris-Pratt)和BM算法(Boyer-Moore).两个算法在最坏情况下均具有线性的查找时间.但是在实用上,KMP算法并不比最简 ...
- 字符串匹配算法(BF算法KMP算法)
字符串匹配算法 暴力匹配(BF)算法 KMP算法 next数组 求next数组的练习 next数组的优化(nextval数组) 练习 暴力匹配(BF)算法 BF算法,即暴力(Brute Force)算 ...
最新文章
- 中科院调查组成立!杨辉发表声明,并对举报信作出详细回应
- 非侵入脑机接口新突破!用意念控制光标,连续追踪效果提升5倍
- 覆盖分类的方法_老罗讲分类|垃圾分类回收模式到底用哪种好?
- python编程视频-【科研资源03】最全Python编程全套系统视频学习教程
- POJ 1611 -The Suspects (并查集)
- 小明分享|8ms平台下工程源码分析
- 【若依(ruoyi)】解决同一Tomcat下两个/多个若依(ruoyi)项目部署报错
- pyspark连接mysql
- WIN7 系统破解LoadRunner 11
- Spring基于注解的方式二
- 雷军穿上印度传统服装:网友以为《西游记》拍新版了 这画面感受下
- Dubbo学习总结(10)——里程碑式 Dubbo 2.7.5 版本发布,性能提升30%,支持 HTTP/2、TLS、Protobuf等特性。
- 华为hcip认证考试题库有哪些内容?华为hcip认证考试题库试题举例
- LLVM PASS类pwn题入门
- 网络编程项目——在线电子词典
- Creo参数曲面设计视频教程
- vue一维码,二维码生成
- arm linux关机命令,嵌入式Linux的关闭命令是什么?
- PyQt5学习—2时间与日期
- Linux下分割、合并PDF(pdftk)
热门文章
- 子控件的鼠标消息怎么传递给父控件?
- [Warning] IPv4 forwarding is disabled. Networking will not work.
- 【非线性光纤光学】,第四章第4题,我的解答,画出KDP晶体的角度调谐曲线
- 面经手册 · 第7篇《ArrayList也这么多知识?一个指定位置插入就把谢飞机面晕了!》
- matlab机器人雅可比矩阵实验,机械臂通过雅可比矩阵实现正运动学及逆运动学迭代解(工具:matlab)...
- POWERLINK 工业实时以太网协议简介
- 复旦女博士于娟:为啥是我得癌症?
- 关于img标签的相对路径和绝对路径以及a标签的一些属性问题
- 大华城市安防监控系统平台管理存在任意文件下载漏洞
- xftp、使用pure-ftpd搭建FTP服务