【算法】Boyer-Moore 算法

目录

  • 1.概述
    • 1.1.Boyer-Moore 算法介绍
    • 1.2.坏字符规则表
    • 1.3.好后缀规则表
    • 1.4.总结
  • 2.代码实现
  • 3.应用

更多数据结构与算法的相关知识可以查看数据结构与算法这一专栏。

有关字符串模式匹配的其它算法:
【算法】Brute-Force 算法
【算法】KMP 算法
【算法】Rabin-Karp 算法

1.概述

1.1.Boyer-Moore 算法介绍

(1)Boyer-Moore 算法又称为 Boyer-Moore 字符串搜索算法(下文简称为 BM 算法),由 Robert S. Boyer 和 J Strother Moore 于1977年提出,它是一种高效的字符串匹配算法,用于在一个目标串中查找一个模式串的出现位置。它的核心思想是通过预处理模式串,利用字符比较的不匹配信息来跳过尽可能多的目标字符,从而快速定位可能的匹配位置,以减少比较次数

(2)BM 算法的主要思想是从模式串(要搜索的字符串)的末尾开始匹配,然后根据不匹配字符在模式串中的位置,跳过一些不必要的比较。具体来说,BM 算法通过预处理模式串,构建两个表:

  • 坏字符规则表:用于记录模式串中每个字符的最后出现位置。在匹配过程中,当发生不匹配时,根据坏字符规则表决定将模式串向右滑动 badShift 位。
  • 好后缀规则表:记录模式串中每个后缀子串的匹配长度。当模式串的后缀子串与主串的某个子串匹配时,可以使用好后缀规则表决定将模式串向右滑动 goodShift 位。

当发生不匹配时,模式串向右滑动的位数为 max(badShift, goodShiftf)。下面将具体介绍这两种规则表。

1.2.坏字符规则表

(1)当目标串中的某个字符跟模式串的某个字符不匹配时,我们称目标串中的这个不匹配字符为坏字符,此时模式串需要向右移动 badShift 位,并且 badShift = 当前对比的字符在模式串中的位置 - 坏字符在模式串中最右出现的位置,如果该模式串中不存在该坏字符,那么最右出现的位置设置为 -1。例如,在下图中,从模式串的末尾开始匹配,显然字符 F 与 字符 B 不匹配,因此字符 F 便为坏字符。
在这里插入图片描述

(2)而上面模式串中并没有字符 F,因此由上面的公式可得 badShift = 5 - (-1) = 6,如下图所示:

在这里插入图片描述

(3)之所以在遇到坏字符时模式串需要向后移动 badShift 位,其原因在于对于当前坏字符来说:

  • 如果模式串中没有该坏字符,那么说明模式串中的任何一个字符都不可能与其匹配成功,所以此时模式串的第一个字符应该直接目标串中该坏字符的后一个字符对齐,即上图中模式串中的字符 A 与目标串中的字符 C 对齐,然后再进行匹配;
  • 如果模式串中有该坏字符,我们需要选取模式串中最右出现的该坏字符,并且将它们对齐。之所以要选取最右字符,其原因在于防止漏掉正确答案,具体例子如下:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

(4)在代码实现中,我们可以用数组来存储模式串中每个字符最右出现的下标,并且模式串中不存在的字符的下标默认为 -1。而坏字符规则表的大小一般为 256,其原因在于在 ASCII 编码中,总共有 256 个字符,每个字符的取值范围是 0 - 255。通过使用 256 大小的坏字符规则表,可以覆盖所有可能的字符。

1.3.好后缀规则表

(1)在 BM 算法中,好后缀是指在模式串中与主串匹配的后缀子串,例如下图中的 “ABC” 即为好后缀。

在这里插入图片描述

(2)而好后缀规则表 goodTable 一般用数组表示,其长度等于模式串的长度(记为 m),并且 goodTable[i] 表示好后缀 t[i + 1…m - 1] 的后缀子串能够匹配模式串中的其它最长子串 substring 时,模式串需要移动的位数,我们需要注意:

  • 如果是好后缀本身与 substring 匹配,那么对 substring 没有什么限制(当然不能是好后缀本身),但是当存在多个匹配的 substring 时,我们应该选择最右出现的 substring,其理由与上面类似,主要是为了防止漏掉正确答案。
  • 如果是好后缀的后缀子串且不是好后缀本身与 substring 匹配,那么 substring 必须是模式串的前缀子串,其原因在于如果 substring 不是前缀子串(它一定是最长的),那么它的前一个字符必然和与其匹配的后缀子串的前一个字符不相等,这样一来滑动到当前位置的模式串就没有匹配的必要了。

例如,上图中好后缀 “ABC” 以及其后缀子串指 “ABC”、“C”、“BC” 这三个字符串,那么显然它们能够匹配模式串的其它最长子串为 “BC”(由于是后缀子串 “BC” 进行匹配的,因此 “BC” 必须是模式串的前缀子串),即 goodTable[3] = 5(此处的下标 3 对应模式串中的字符 D 的下标),如下图所示:
在这里插入图片描述

再例如,下图中好后缀 “ABC” 显然可以与模式串中的最长子串 “ABC”(绿色填充部分)匹配,即 goodTable[3] = 3(此处的下标 3 对应模式串中的字符 C 的下标)。

在这里插入图片描述

在这里插入图片描述

(3)虽然可以直接使用 goodTable 记录遇到好后缀时模式串需要移动的位数(其代码实现如下),但是求解 goodTable 的时间复杂度却达到了 O(n3)(两层 for 循环加上 equals 方法),这样一来效率较低。

public int[] getGoodTable(String t) {int m = t.length();int[] goodTable = new int[m];Arrays.fill(goodTable, m);for (int i = 1; i < m; i++) {// suffix 为好后缀 t[i...m - 1]String suffix = t.substring(i);// suffix 长度int suffixLen = m - i;for (int j = m - 1; j >= m - suffixLen; j--) {if (j >= suffixLen) {//判断好后缀本身是否与其它子串匹配if (t.substring(j - suffixLen, j).equals(suffix)) {goodTable[m - i - 1] = m - j;break;}} else {//判断好后缀的后缀子串是否与前缀子串匹配if (t.substring(0, j).equals(suffix.substring(suffixLen - j))) {goodTable[m - i - 1] = m - j;break;}}}}return goodTable;
}

(4)实际上,我们可以使用另一种方法将时间复杂度降低到 O(n2),我们可以使用长度为 m 的数组 suffix 来保存每个不同长度的好后缀匹配的其它最长靠右子串的起始位置,以模式串 t = “ABCDABC” 为例:

好后缀长度suffix解释
C1suffix[1] = 2长度为 1 的好后缀 C 与起始下标为 2 的子串 C 匹配,并且满足最长和靠右这两个要求
BC2suffix[2] = 1长度为 2 的好后缀 BC 与起始下标为 1 的子串 BC 匹配,并且满足最长和靠右这两个要求
ABC3suffix[3] = 0长度为 3 的好后缀 ABC 与起始下标为 0 的子串 BC 匹配,并且满足最长和靠右这两个要求
DABC4suffix[4] = -1好后缀 DABC 不与任何其它子串匹配,标记为 -1
CDABC5suffix[5] = -1好后缀 CDABC 不与任何其它子串匹配,标记为 -1
BCDABC6suffix[6] = -1好后缀 BCDABC 不与任何其它子串匹配,标记为 -1

求解数组 suffix 的代码如下,其时间复杂度为 O(n2)。

int m = t.length();
int[] suffix = new int[m];
Arrays.fill(suffix, -1);
for (int i = 0; i < m - 1; i++) {int j = i;int k = 0;while (j >= 0 && t.charAt(j) == t.charAt(m - 1 - k)) {j--;k++;suffix[k] = j + 1;}
}

(5)如果我们通过数组 suffix 知道了当前好后缀并不能匹配其它子串时,那么如何判断它的后缀子串是否可以与模式串的前缀子串进行匹配呢?其实,解决办法也比较简单:

  • 如果 suffix[k] != -1,那么说明当前好后缀 t[j + 1...m - 1] 可以匹配其它最长靠右子串,并且该子串的起始位置为 suffix[k],此时模式串需要向右移动 goodShift = j - suffix[k] + 1 位,其中 j 与好后缀长度 k 的关系为 k = m - 1 - j
  • 如果 suffix[k] == -1,那么说明当前好后缀 t[j + 1...m - 1] 没有可以匹配的其它最长靠右子串,此时,我们需要从该好后缀的最长后缀子串 t[r...m - 1] 开始遍历(其中 r = j + 2),寻找与之匹配的最长前缀。判断方法也比较简单,即判断 suffix[m - r] 是否为 0 即可:
    • 如果 suffix[m - r] == 0,则说明该后缀子串与长度为 m - r 前缀的匹配,此时模式串需要向右移动 goodShift = r 位;
    • 如果 suffix[m - r] != 0,则说明该后缀子串与长度为 m - r 前缀的不匹配,判断下一个后缀子串即可;
  • 如果上面两种情况都没有匹配成功,则直接返回模式串的长度,具体代码如下所示:
private static int moveByGs(int j, int m, int[] suffix) {// k 为当前好后缀 t[j + 1...m - 1] 的长度int k = m - 1 - j;if (suffix[k] != -1) {//好后缀可以直接匹配return j - suffix[k] + 1;}//好后缀不能直接匹配,那么寻找与好后缀的后缀子串匹配的模式串的最长前缀for (int r = j + 2; r <= m - 1; r++) {//判断后缀子串 t[r...m - 1] 匹配的最长靠右子串的起始位置是否为 0if (suffix[m - r] == 0) {return r;}}//上面两种情况都没有匹配成功,则直接返回模式串的长度return m;
}

1.4.总结

在匹配过程中发生不匹配时,我们可以根据上面计算得到的坏字符规则表和好后缀规则表,来分别计算模式串应该向右滑动的最大位数 badShiftgoodShift,并且都能保证不会漏掉正确答案。所以在 BM 算法中,每次发生不匹配时,模式串最终向右滑动的位数为 max(badShift, goodShift),即两者之间的最大值。

2.代码实现

(1)BM 算法的代码实现如下:

class BmAlgorithm {private final static int ASCII_SIZE = 256;public static int bmSearch(String s, String t) {int n = s.length();int m = t.length();if (m == 0) {return 0;}//坏字符规则表 badTable: 记录模式串中每个字符最后出现的位置int[] badTable = new int[ASCII_SIZE];Arrays.fill(badTable, -1);for (int i = 0; i < t.length(); i++) {badTable[t.charAt(i)] = i;}/** 好后缀规则表 goodTable: 用数组 suffix 表示* suffix[i] 表示长度为 i 的好后缀匹配的最长靠右子串的起始位置* */int[] suffix = new int[m];Arrays.fill(suffix, -1);for (int i = 0; i < m - 1; i++) {int j = i;int k = 0;while (j >= 0 && t.charAt(j) == t.charAt(m - 1 - k)) {j--;k++;suffix[k] = j + 1;}}int i = 0;while (i <= n - m) {//从右往左开始匹配int j = m - 1;while (j >= 0 && t.charAt(j) == s.charAt(i + j)) {j--;}if (j < 0) {//匹配成功return i;} else {//根据坏字符规则计算要移动的位数int badShift = j - badTable[s.charAt(i + j)];//根据好字符规则计算要移动的位数int goodShift = 0;//如果 j == m - 1,即好后缀为 "",此时需要遵循坏字符规则if (j < m - 1) {goodShift = moveByGs(j, m, suffix);}i += Math.max(badShift, goodShift);}}//无法匹配return -1;}private static int moveByGs(int j, int m, int[] suffix) {// k 为当前好后缀 pattern[j + 1...m - 1] 的长度int k = m - 1 - j;if (suffix[k] != -1) {//好后缀可以直接匹配return j - suffix[k] + 1;}//好后缀不能直接匹配,那么寻找与好后缀的后缀子串匹配的模式串的最长前缀for (int r = j + 2; r <= m - 1; r++) {//判断后缀子串 pattern[r...m - 1] 匹配的最长靠右子串的起始位置是否为 0if (suffix[m - r] == 0) {return r;}}//上面两种情况都没有匹配成功,则直接返回模式串的长度return m;}
}

(2)测试代码如下:

class BmAlgorithmTest {public static void main(String[] args) {String text = "hello world AABCABC";String pattern = "ABCDABC";int index = bmSearch(text, pattern);if (index != -1) {System.out.println("Pattern found at index: " + index);} else {System.out.println("Pattern not found.");}}
}

输出结果为:

Pattern found at index: 16

注意:如果想找出所有匹配成功的位置,只需要简单改一下上面的代码即可,当匹配成功时,我们可以使用 list 来存储当前匹配成功的位置,当匹配结束后,返回 list 即可。

3.应用

BM 算法适用于各种类型的字符串匹配问题,具体来说,BM算法在以下情况下表现优秀:

  • 目标串较长:BM 算法适用于处理大规模文本串的情况,因为它能够利用坏字符规则和好后缀规则在较少的比较操作中快速跳过大量的字符。
  • 模式串较短:相对于文本串,模式串较短的情况下,BM 算法的性能更为突出,因为它能够利用坏字符规则和好后缀规则快速跳过无需比较的部分。
  • 重复字符较少:如果模式串中包含大量重复字符,BM 算法的好后缀规则会出现退化的情况,导致性能下降。因此,当模式串中的重复字符较少时,BM算法能够发挥最佳性能。

总之,BM算法适用于大规模文本串和较短、重复字符较少的模式串的字符串匹配问题。它在实际中被广泛应用于文本编辑器、搜索引擎、数据处理等领域。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/196526.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数据在内存中的存储(含面试题)

数据在内存中的存储 1. 整数在内存中的存储2. 大小端字节序和字节序判断2.1 什么是大小端&#xff1f;2.2 为什么有大小端?2.3 练习2.3.1 练习12.3.2 练习22.3.3 练习3第一题第二题 2.3.4 练习42.3.5 练习5第一题第二题 2.3.6 练习6 1. 整数在内存中的存储 在讲解操作符的时候…

QNX的nicinfo ifmcstat if_up和tcpdump

nicinfo 在QNX操作系统中&#xff0c;nicinfo是一个用于显示网络接口卡&#xff08;NIC&#xff09;信息的命令行工具。它可以提供有关系统中所有可用网络接口卡的详细信息&#xff0c;例如接口名称、MAC地址、IP地址、掩码、广播地址、传输单元大小等等。 通过nicinfo命令可…

android studio 打开flutter项目 出现 dart sdk is not configured

android studio 版本 flutter版本 解决方式 1 点击Open Dart setting 2 打勾Enable Dart support for the project 3 Dart SDK path 选择flutter/bin/cache/dart-sdk 4 打勾Enable Dart support for the following modules

Python标准库:math模块【侯小啾Python基础领航计划 系列(十六)】

Python标准库:math模块【侯小啾python基础领航计划 系列(十六)】 大家好,我是博主侯小啾, 🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ…

Linux的sed命令

环境 Ubuntu 22.04 概述 sed 是“stream editor”的意思&#xff0c;用来处理文本&#xff0c;比如做文本查找、替换。 处理单行文本 用法1 语法&#xff1a; sed s/aaaa/bbbb/ aaaa &#xff1a;用正则表达式来匹配字符串&#xff0c;并用圆括号括起来需要获取的内容&a…

TCPDUMP抓包明确显示IP地址和端口号

经常使用tcpdump进行抓包的同学可以忽略了&#xff0c;这篇偏于使用扫盲&#xff1b;首先&#xff0c;tcpdump抓包目的IP显示为hostname&#xff0c;如果端口是知名端口&#xff0c;显示为协议名而不是端口号。这种默认其实略有问题的&#xff1a; 如果我们使用默认的hostname…

【蓝桥杯】马的遍历

马的遍历 题目描述 有一个 n m n \times m nm 的棋盘&#xff0c;在某个点 ( x , y ) (x, y) (x,y) 上有一个马&#xff0c;要求你计算出马到达棋盘上任意一个点最少要走几步。 输入格式 输入只有一行四个整数&#xff0c;分别为 n , m , x , y n, m, x, y n,m,x,y。 …

LeetCode算法练习top100:(6)图论

package top100.图论;import java.util.LinkedList; import java.util.Queue;public class TOP {//200. 岛屿数量//网格类问题的 DFS 遍历方法int[][] ways new int[][]{{-1, 0}, {1, 0}, {0, 1}, {0, -1}};public int numIslands(char[][] grid) {int m grid.length, n gri…

目标检测——R-CNN系列检测算法总结

R-CNN系列算法详细解读文章&#xff1a; R-CNN算法解读SPPNet算法解读Fast R-CNN算法解读Faster R-CNN算法解读Mask R-CNN算法解读 目录 1、概述1.1 获取目标候选框1.2 候选框提取特征1.3 候选框分类及边框回归 2、R-CNN系列算法概述2.1 R-CNN算法2.2 SPPNet算法2.3 Fast R-CN…

2024最新版软件测试八股文(文档)

前言 &#xff08;第一个就刷掉一大批人&#xff09; 有很多“会自动化”的同学来咨询技术问题&#xff0c;他总会问到我一些元素定位的问题。元素定位其实都不算自动化面试的问题。 一般我都会问&#xff1a;你是定位不到吗&#xff1f;通常结果都是说确实定位不到。 做自…

131.类型题-计算数学序列的和,请编写函数fun,其功能是S=……【满分解题代码+详细分析】(数学序列的和类型题-C/C++JavaPython实现)

文章目录 131.类型题-计算数学序列的和:计算并输出一.题目1.1 解题思路二.解题代码2.1 C/C++解题代码2.2 python解题代码2.3 Java解题代码三.解题代码仔细分析3.1 C/C++解题代码仔细分析3.2 Java解题代码仔细分析3.3 Python解题代码仔细分析四.本类型题解题诀窍五.寄语131.类型…

Spring Boot 之 ModelFactory

1.initModel 功能&#xff1a; public void initModel(NativeWebRequest request, ModelAndViewContainer container, HandlerMethod handlerMethod)throws Exception {Map<String, ?> sessionAttributes this.sessionAttributesHandler.retrieveAttributes(request);c…

Codeforces Round 910 (Div. 2)

Codeforces Round 910 (Div. 2) 文章目录 Codeforces Round 910 (Div. 2)ABCD A 模拟 #include <bits/stdc.h>using namespace std; const int N1e510; char s[N];void solve(){int n , k , cb 0 , ans 0;cin >> n >> k;for(int i 1 ; i < n ; i ){c…

简易电路特性测试仪

目录 摘 要... 3 第一章 绪论... 5 1.1 研究课题背景... 5 1.2 国内外发展概况... 7 1.3 课题研究的目的... 9 1.4 课题的研究内容及章节安排... 9 第二章 电路特性测试仪的设计方案... 10 2.1 系统总体设计思路... 10 2.2 电路特性测试仪总体设计方案…

HarmonyOS开发上手

首先献出开发官网地址 &#xff08;https://developer.harmonyos.com/cn/develop/&#xff09; 本文内容 基础入门内容介绍安装DevEco StudioDevEco Studio常用功能介绍项目工程结构详解 1. 基础入门内容介绍 应用开发流程 在正式开始之前还需要了解一些有关的基础概念 方舟…

【PyTorch】概述

文章目录 1. PyTorch是什么&#xff1f;2. PyTorch的特点3. PyTorch的架构 1. PyTorch是什么&#xff1f; PyTorch是一个深度学习框架&#xff0c;由Facebook于2016年开源发布。PyTorch是基于Torch框架的Python接口&#xff0c;旨在提供易用的强大工具来进行神经网络的构建和训…

python实现从远程服务器读取 JSON 文件、解析内容并将其存储到 MySQL 表中,然后删除已解析的文件

创建一个 Python 脚本&#xff0c;利用 Paramiko 库连接到远程服务器&#xff0c;读取 JSON 文件、解析内容并将其存储到 MySQL 表中&#xff0c;最后删除文件。 import paramiko import json import MySQLdb import os# SSH 连接参数 ssh_client paramiko.SSHClient() ssh_c…

H5流媒体播放器EasyPlayer播放H.265新增倍速播放功能,具体如何实现?

目前我们TSINGSEE青犀视频所有的视频监控平台&#xff0c;集成的都是EasyPlayer.js版播放器&#xff0c;它属于一款高效、精炼、稳定且免费的流媒体播放器&#xff0c;可支持多种流媒体协议播放&#xff0c;包括WebSocket-FLV、HTTP-FLV&#xff0c;HLS&#xff08;m3u8&#x…

做项目碰到的一些安卓与苹果的不兼容,做个记录

默认字体 // 苹果手机默认字体为 font-family:simsun; // 安卓和H5默认字体为 font-family: initial;屏幕截图(html-to-image) // 这里只做js部分(vue3使用)import * as htmlToImage from html-to-image;let imgcanvas ref() // 图片标签img的src指向const captureScreen ()…

vivado分析-在 Versal 器件中执行 NoC 服务质量分析

AMD Vivado ™ 中的服务质量 (QoS) 用于将片上网络 (NoC) 编译器生成的当前 NoC 解决方案估算所得 QoS 与 AXI NoCIP 和 / 或 AXI4 ‑ Stream NoC IP 中指定的 QoS 要求进行对比。一旦 NoC 解决方案过时 &#xff0c; 就需要调用 NoC 编译器并生成新的 NoC 解决方案以…