字符串匹配算法(二):BM(BoyerMoore)算法、坏字符规则,好后缀规则
文章目录
- BM算法
- 坏字符规则
- 好后缀规则
- 完整代码
BM算法
BM算法的全程叫做Boyer-Moore,是工程上最常用且最高效的字符串匹配算法,有实验统计,它的性能是著名的KMP 算法的 3 到 4 倍。那么它是如何将性能提升的呢?
在上一篇博客中我介绍了BF算法和RK算法,其中也提到过如果想要优化字符串匹配的效率,就必须要减少不必要的比较,例如RK算法就是通过预匹配哈希值来完成了这一功能,但是我们也提到了,由于哈希冲突等原因,RK在最坏的情况下就会退化成BF算法。
基于上述问题的缺陷,BM、KMP(下一篇博客会写)等算法采用了大量滑动的机制来解决这一问题
在RK和BF算法中,在字符串不匹配的时候,我们通常会将模式串滑动到主串的下一个位置继续进行匹配,这种方法存在一定的缺陷,就是即使我们滑动到的位置不可能完成匹配,我们还是会一个一个去尝试进行配对,这也就是它们效率低下的原因。
就例如上图,我们可以发现a只存在于主串的第一个位置,第四个位置,第六个位置,而其他的位置下模式串是不可能匹配成功的,所以我们滑动的时候就应该直接滑动到上述的位置,如下图
BM算法的核心就是找到这种大量滑动的规律,减少无意义的匹配。而它正是通过坏字符规则与好后缀规则来实现。
坏字符规则
因为我们的坏字符和好后缀规则都需要保证偏移量最大,所以其并非像传统的字符串比较一样从前往后,而是从后往前比较,并且我们将第一个遇见的不匹配的字符称为坏字符
当检测到坏字符后,我们就没有必要再一个一个的进行判断了,因为只有模式串与坏字符T对齐的位置也是字符T的情况下,两者才有匹配的可能。并且为了保证滑动的范围最大,我们对字符T的选择是在模式串中最后一次出现的那个
坏字符规则主要有以下三种情况,下面一一对其进行分析
- 模式串中存在与坏字符相同的字符
- 模式串中不存在与坏字符相同的字符
- 模式串中存在的其他坏字符为第一个字符
情况一:模式串中存在与坏字符相同的字符
此时的处理方法就是将坏字符与匹配字符对其,接着进行判断
此时匹配成功
情况二:模式串中不存在与坏字符相同的字符
此时模式串中不存在可以与坏字符匹配的字符,这也就代表着在坏字符这个位置之前,不可能匹配成功,所以我们直接滑动到坏字符的下一个位置
此时,匹配成功。
情况三:倒退或者不移动
例如以下情景
后面的全部匹配,不匹配的只有b,而坏字符又在最后面出现过,此时就会倒退。
所以我们还需要加上判断,如果滑动值小于等于0时,就直接向后滑动一步。
为了保存每个字符的最后一次出现的下标,我们使用一个数组来模拟哈希,采用ascii码来进行直接定址
下面是基于坏字符规则实现的BM算法
//构建坏字符规则的下标数组
void generateBC(const string& pattern, int* indexArr, int len)
{//初始化for(int i = 0; i < len; i++){indexArr[i] = -1;}//记录模式串中每个下标最后出现的位置for(int i = 0; i < pattern.size(); i++){indexArr[pattern[i]] = i; }
}int boyerMoore(const string& str, const string& pattern)
{//不满足条件则直接返回falseif(str.empty() || pattern.empty() || str.size() < pattern.size()){return -1;}int len1 = str.size(), len2 = pattern.size();int indexArr[128] = {0}; //坏字符规则记录数组,记录了每一个字符最后一次出现的下标generateBC(pattern, indexArr, 128);int i = 0;while(len1 - i >= len2){int j;//模式串从后往前匹配for(j = len2 - 1; j >= 0; j--){//如果当前字符不匹配,则说明该位置是坏字符if(str[i + j] != pattern[j]){break;}}//如果全部匹配,则返回主串起始位置if(j < 0){return i;}/*如果该字符没出现过,则直接将模式串滑动到坏字符的下一个位置如果出现过,则将模式串中对应字符滑动到坏字符处*/int badMove = (j - indexArr[str[i + j]]);badMove = (badMove == 0) ? 1 : badMove; //防止倒退i += badMove;}return -1;
}
从上面也可以看出,在最后一种情况下BM算法的效率就又会退化到BF算法的级别,所以为了防止这种问题,BM还有一种好前缀规则
好后缀规则
在我们进行匹配的时候,我们将第一次碰到的不匹配的字符称为坏字符,而将碰到坏字符之前所匹配到的字符串称为好后缀
与坏字符规则一样,如果我们想要使得字符串匹配,只有模式串中存在相同子串,并与主串中好后缀对齐的情况下,两者才有匹配的可能。所以直接将对应子串滑动到好后缀的位置,如下图
如果不存在这个子串,那我们能否按照坏字符规则,则直接跳过好后缀后呢?
答案是否定的,如果我们因为过度滑动导致我们跳过了本身可匹配的一些字符串,如下图
这是为什么呢?虽然我们的模式串中并不存在能够与好后缀匹配的子串,但是却存在能够与好后缀部分重合的子串,而我们的滑动就导致了跳过了这些子串
为了防止上述情况,我们此时就会寻找能够与部分好后缀子串匹配的前缀,并以它为滑动的标准,如下图
为了方便计算,我们需要保存模式串中所有前缀和后缀的匹配情况以及子串的位置,所以需要引入两个数组,一个是整型数组suffix,其用于标记能够与好后缀匹配的子串的下标。另一个是布尔数组prefix,其用于标记前缀[0, i - 1]是否能够与好后缀进行匹配。
//构建好后缀规则的前缀和后缀数组
void generateGS(const string& pattern, vector<int>& suffix, vector<bool>& prefix)
{int len = suffix.size();//匹配区间[0 ~ len - 1],len时即为整个模式串,不可能存在前缀for(int i = 0; i < len - 1; i++){int j = i;int size = 0; //匹配子串的下标while(j >= 0 && pattern[j] == pattern[len - 1 - size]){//继续匹配下一个位置j--; size++; suffix[size] = j + 1; //记录匹配后缀的子串的位置}//如果子串一直匹配到开头,则说明该子串为前缀,此时前缀与后缀匹配if(j == -1){prefix[size] = true;}}
}//计算出好后缀规则的偏移量
int moveByGS(int index, const vector<int>& suffix, const vector<bool>& prefix)
{int len = suffix.size(); //模式串长度int size = len - 1 - index; //后缀长度//如果存在与后缀匹配的子串,则直接返回它们的偏移量if(suffix[size] != -1){return index - suffix[size] + 1;}//如果没有匹配的后缀,那么判断后缀中是否有部分与前缀匹配for(int i = index + 2; i <= len - 1; i++){if(prefix[len - i] == true){return i;}}//如果也不存在,则说明没有任何匹配,直接偏移整个模式串的长度return len;
}
完整代码
好前缀和坏字符都实现了,因为我们希望的是尽量减少不必要的匹配,所以我们选取两者中较大的那一个作为偏移量。
将上面的好前缀规则加入前面写的坏字符规则的框架中,就是完整的BM算法,代码如下
//构建坏字符规则的下标数组
void generateBC(const string& pattern, vector<int>& indexArr)
{//记录模式串中每个下标最后出现的位置for(int i = 0; i < pattern.size(); i++){indexArr[pattern[i]] = i; }
}//构建好后缀规则的前缀和后缀数组
void generateGS(const string& pattern, vector<int>& suffix, vector<bool>& prefix)
{int len = suffix.size();//匹配区间[0 ~ len - 1],len时即为整个模式串,不可能存在前缀for(int i = 0; i < len - 1; i++){int j = i;int size = 0;while(j >= 0 && pattern[j] == pattern[len - 1 - size]){//继续匹配下一个位置j--; size++; suffix[size] = j + 1; //记录匹配后缀的子串的位置}//如果子串一直匹配到开头,则说明该子串为前缀,此时前缀与后缀匹配if(j == -1){prefix[size] = true;}}
}int moveByGS(int index, const vector<int>& suffix, const vector<bool>& prefix)
{int len = suffix.size(); //模式串长度int size = len - 1 - index; //后缀长度//如果存在与后缀匹配的子串,则直接返回它们的偏移量if(suffix[size] != -1){return index - suffix[size] + 1;}//如果没有匹配的后缀,那么判断后缀中是否有部分与前缀匹配for(int i = index + 2; i <= len - 1; i++){if(prefix[len - i] == true){return i;}}//如果也不存在,则说明没有任何匹配,直接偏移整个模式串的长度return len;
}int boyerMoore(const string& str, const string& pattern)
{//不满足条件则直接返回falseif(str.empty() || pattern.empty() || str.size() < pattern.size()){return -1;}int len1 = str.size(), len2 = pattern.size();vector<int> indexArr(128, -1); //标记匹配坏字符的字符下标vector<int> suffix(len2, -1); //标记匹配后缀的子串下标vector<bool> prefix(len2, false); //标记是否匹配前缀generateBC(pattern, indexArr);generateGS(pattern, suffix, prefix);int i = 0;while(len1 - i >= len2){int j;//模式串从后往前匹配for(j = len2 - 1; j >= 0; j--){//如果当前字符不匹配if(str[i + j] != pattern[j]){break;}}//如果全部匹配,则返回主串起始位置if(j < 0){return i;}int badMove = (j - indexArr[str[i + j]]); //坏字符规则偏移量badMove = (badMove == 0) ? 1 : badMove; //防止倒退int goodMove = 0; //好后缀规则偏移量//如果一个都不匹配,则不存在后缀if(j < len2 - 1){goodMove = moveByGS(j, suffix, prefix); //计算出好后缀的偏移量}i += max(goodMove, badMove); //加上最大的那个}return -1;
}
字符串匹配算法(二):BM(BoyerMoore)算法、坏字符规则,好后缀规则相关推荐
- 大量的数据做字符串匹配_【重学数据结构与算法(JS)】字符串匹配算法(三)——BM算法...
前言 文章的一开头,还是要强调下字符串匹配的思路 将模式串和主串进行比较 从前往后比较 从后往前比较 2. 匹配时,比较主串和模式串的下一个位置 3. 失配时, 在模式串中寻找一个合适的位置 如果找到 ...
- diff算法阮一峰_【重学数据结构与算法(JS)】字符串匹配算法(三)——BM算法
前言 文章的一开头,还是要强调下字符串匹配的思路 将模式串和主串进行比较 从前往后比较 从后往前比较 2. 匹配时,比较主串和模式串的下一个位置 3. 失配时, 在模式串中寻找一个合适的位置 如果找到 ...
- 字符串匹配算法之BM算法
BM算法,全称是Boyer-Moore算法,1977年,德克萨斯大学的Robert S. Boyer教授和J Strother Moore教授发明了一种新的字符串匹配算法. BM算法定义了两个规则: ...
- 一看就懂的字符串匹配算法 之 BM算法
先来一个导读,关于BM算法开始有了难度,大家一定要静下心,咬着呀也得给我读下去,这样你才能有收获. 我们在文本编辑器中,我们经常用到查找及替换功能.比如说,在Word文件中,通过查找及替换功能,可以把 ...
- 字符串匹配算法(BM)
文章目录 1. BM(Boyer-Moore)算法 1.1 坏字符规则 1.2 好后缀规则 1.3 两种规则如何选择 2. BM算法代码实现 2.1 坏字符 2.2 好后缀 2.3 完整代码 2.4 ...
- 字符串匹配算法BF,BM,KMP
字符串匹配bf算法:(暴力穷举算法) 在一个字符串中寻找另一字符串,最容易想到的,也是最简单的办法是:取主串和模式串/搜索串中的每一位依次比较,如果匹配则同时后移一位继续比较,直至匹配到模式串的最后一 ...
- php二维数组拆分成字符串,PHP二维数组切割为字符串并去除重复的值
本篇文章的内容是关于PHP二维数组切割为字符串并去除重复的值 的代码,现在分享给大家,有需要的朋友可以参考一下 应用场景在于需要查询出某一个rent_contract_id所有有关的id及rent_c ...
- 字符串匹配算法 -- BM(Boyer-Moore) 和 KMP(Knuth-Morris-Pratt)详细设计及实现
文章目录 1. 算法背景 2. BM(Boyer-Moore)算法 2.1 坏字符规则(bad character rule) 2.2 好后缀规则(good suffix shift) 2.3 复杂度 ...
- 列宽一字符等于多少厘米_字符串匹配算法总结——BF、KMP、BM
说明 以下算法介绍中,被匹配字符串称为主串,匹配模式字符串称为匹配串,索引从0开始. 前缀数组:字符串S = AB(B !== ⏀,即B为任一非空字符串) ,S的前缀指A.前缀数组指所有包含第一个字符 ...
最新文章
- 以Attention Model为例谈谈两种研究创新模式
- sqap不支持python3吗_Supporting Python 3(支持python3)——欢迎来到Python 3
- boost::with_lock_guard相关的测试程序
- c/c++面试试题(一)
- python中numpy、matplotlib的引入及测试
- html怎么让背景颜色百分比,jquery – CSS设置背景颜色只是表行宽度的一个百分比...
- php pdoconnection,php使用pdo连接报错Connection failed SQLSTATE的解决方法
- 淘宝天猫获取商品类目信息api接口数据获取
- eclipse语言包安装太慢,或者卡住不动的解决方法
- java打开dex文件_dex文件反编译工具(Dedexer)
- 安卓按键命令库教程(紫猫版续)
- Android Muti-Window
- 和娃一起过暑假:一次4000+km自驾的尝试
- 电脑中显示dns服务器可能不可用,Win7网络诊断“DNS服务器可能不可用”怎么解决?-电脑自学网...
- Torque2D MIT 实战记录: Isometric(等轴视距)
- 使用TreeView树状图
- 机器学习系列5-梯度下降法
- Unity学习资源指南[精心整理]
- 百度分享不支持https解决方法
- 物联网大数据存储利器IoTDB介绍
热门文章
- Eurek Ribbon Feign常见问题及解决
- 数据库设计对性能的影响
- 单列设计模式 懒汉式及多线程debug
- Spring Security源码解析(一)——认证和鉴权
- Cortex‐M3-总线接口
- Puppet基于Master/Agent模式实现LNMP平台部署
- Android特效专辑(十二)——仿支付宝咻一咻功能实现波纹扩散特效,精细小巧的View...
- LAPM×××和php加速器
- 8587520在51CTO【礼树迎蛇 红满社区】
- org.apache.jasper.JasperException: Unable to co...