文章目录

  • BM算法
  • 坏字符规则
  • 好后缀规则
  • 完整代码

BM算法

BM算法的全程叫做Boyer-Moore,是工程上最常用且最高效的字符串匹配算法,有实验统计,它的性能是著名的KMP 算法的 3 到 4 倍。那么它是如何将性能提升的呢?

在上一篇博客中我介绍了BF算法和RK算法,其中也提到过如果想要优化字符串匹配的效率,就必须要减少不必要的比较,例如RK算法就是通过预匹配哈希值来完成了这一功能,但是我们也提到了,由于哈希冲突等原因,RK在最坏的情况下就会退化成BF算法。

基于上述问题的缺陷,BM、KMP(下一篇博客会写)等算法采用了大量滑动的机制来解决这一问题

在RK和BF算法中,在字符串不匹配的时候,我们通常会将模式串滑动到主串的下一个位置继续进行匹配,这种方法存在一定的缺陷,就是即使我们滑动到的位置不可能完成匹配,我们还是会一个一个去尝试进行配对,这也就是它们效率低下的原因。

就例如上图,我们可以发现a只存在于主串的第一个位置,第四个位置,第六个位置,而其他的位置下模式串是不可能匹配成功的,所以我们滑动的时候就应该直接滑动到上述的位置,如下图

BM算法的核心就是找到这种大量滑动的规律,减少无意义的匹配。而它正是通过坏字符规则好后缀规则来实现。


坏字符规则

因为我们的坏字符和好后缀规则都需要保证偏移量最大,所以其并非像传统的字符串比较一样从前往后,而是从后往前比较,并且我们将第一个遇见的不匹配的字符称为坏字符

当检测到坏字符后,我们就没有必要再一个一个的进行判断了,因为只有模式串与坏字符T对齐的位置也是字符T的情况下,两者才有匹配的可能。并且为了保证滑动的范围最大,我们对字符T的选择是在模式串中最后一次出现的那个

坏字符规则主要有以下三种情况,下面一一对其进行分析

  1. 模式串中存在与坏字符相同的字符
  2. 模式串中不存在与坏字符相同的字符
  3. 模式串中存在的其他坏字符为第一个字符

情况一:模式串中存在与坏字符相同的字符

此时的处理方法就是将坏字符与匹配字符对其,接着进行判断

此时匹配成功

情况二:模式串中不存在与坏字符相同的字符

此时模式串中不存在可以与坏字符匹配的字符,这也就代表着在坏字符这个位置之前,不可能匹配成功,所以我们直接滑动到坏字符的下一个位置

此时,匹配成功。

情况三:倒退或者不移动
例如以下情景
后面的全部匹配,不匹配的只有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)算法、坏字符规则,好后缀规则相关推荐

  1. 大量的数据做字符串匹配_【重学数据结构与算法(JS)】字符串匹配算法(三)——BM算法...

    前言 文章的一开头,还是要强调下字符串匹配的思路 将模式串和主串进行比较 从前往后比较 从后往前比较 2. 匹配时,比较主串和模式串的下一个位置 3. 失配时, 在模式串中寻找一个合适的位置 如果找到 ...

  2. diff算法阮一峰_【重学数据结构与算法(JS)】字符串匹配算法(三)——BM算法

    前言 文章的一开头,还是要强调下字符串匹配的思路 将模式串和主串进行比较 从前往后比较 从后往前比较 2. 匹配时,比较主串和模式串的下一个位置 3. 失配时, 在模式串中寻找一个合适的位置 如果找到 ...

  3. 字符串匹配算法之BM算法

    BM算法,全称是Boyer-Moore算法,1977年,德克萨斯大学的Robert S. Boyer教授和J Strother Moore教授发明了一种新的字符串匹配算法. BM算法定义了两个规则: ...

  4. 一看就懂的字符串匹配算法 之 BM算法

    先来一个导读,关于BM算法开始有了难度,大家一定要静下心,咬着呀也得给我读下去,这样你才能有收获. 我们在文本编辑器中,我们经常用到查找及替换功能.比如说,在Word文件中,通过查找及替换功能,可以把 ...

  5. 字符串匹配算法(BM)

    文章目录 1. BM(Boyer-Moore)算法 1.1 坏字符规则 1.2 好后缀规则 1.3 两种规则如何选择 2. BM算法代码实现 2.1 坏字符 2.2 好后缀 2.3 完整代码 2.4 ...

  6. 字符串匹配算法BF,BM,KMP

    字符串匹配bf算法:(暴力穷举算法) 在一个字符串中寻找另一字符串,最容易想到的,也是最简单的办法是:取主串和模式串/搜索串中的每一位依次比较,如果匹配则同时后移一位继续比较,直至匹配到模式串的最后一 ...

  7. php二维数组拆分成字符串,PHP二维数组切割为字符串并去除重复的值

    本篇文章的内容是关于PHP二维数组切割为字符串并去除重复的值 的代码,现在分享给大家,有需要的朋友可以参考一下 应用场景在于需要查询出某一个rent_contract_id所有有关的id及rent_c ...

  8. 字符串匹配算法 -- BM(Boyer-Moore) 和 KMP(Knuth-Morris-Pratt)详细设计及实现

    文章目录 1. 算法背景 2. BM(Boyer-Moore)算法 2.1 坏字符规则(bad character rule) 2.2 好后缀规则(good suffix shift) 2.3 复杂度 ...

  9. 列宽一字符等于多少厘米_字符串匹配算法总结——BF、KMP、BM

    说明 以下算法介绍中,被匹配字符串称为主串,匹配模式字符串称为匹配串,索引从0开始. 前缀数组:字符串S = AB(B !== ⏀,即B为任一非空字符串) ,S的前缀指A.前缀数组指所有包含第一个字符 ...

最新文章

  1. 以Attention Model为例谈谈两种研究创新模式
  2. sqap不支持python3吗_Supporting Python 3(支持python3)——欢迎来到Python 3
  3. boost::with_lock_guard相关的测试程序
  4. c/c++面试试题(一)
  5. python中numpy、matplotlib的引入及测试
  6. html怎么让背景颜色百分比,jquery – CSS设置背景颜色只是表行宽度的一个百分比...
  7. php pdoconnection,php使用pdo连接报错Connection failed SQLSTATE的解决方法
  8. 淘宝天猫获取商品类目信息api接口数据获取
  9. eclipse语言包安装太慢,或者卡住不动的解决方法
  10. java打开dex文件_dex文件反编译工具(Dedexer)
  11. 安卓按键命令库教程(紫猫版续)
  12. Android Muti-Window
  13. 和娃一起过暑假:一次4000+km自驾的尝试
  14. 电脑中显示dns服务器可能不可用,Win7网络诊断“DNS服务器可能不可用”怎么解决?-电脑自学网...
  15. Torque2D MIT 实战记录: Isometric(等轴视距)
  16. 使用TreeView树状图
  17. 机器学习系列5-梯度下降法
  18. Unity学习资源指南[精心整理]
  19. 百度分享不支持https解决方法
  20. 物联网大数据存储利器IoTDB介绍

热门文章

  1. Eurek Ribbon Feign常见问题及解决
  2. 数据库设计对性能的影响
  3. 单列设计模式 懒汉式及多线程debug
  4. Spring Security源码解析(一)——认证和鉴权
  5. Cortex‐M3-总线接口
  6. Puppet基于Master/Agent模式实现LNMP平台部署
  7. Android特效专辑(十二)——仿支付宝咻一咻功能实现波纹扩散特效,精细小巧的View...
  8. LAPM×××和php加速器
  9. 8587520在51CTO【礼树迎蛇 红满社区】
  10. org.apache.jasper.JasperException: Unable to co...