1. 算法简介
Boyer-Moore 算法是一种高效的字符串匹配算法,用于在文本(Text)中快速查找模式(Pattern)的位置。其核心思想是:
- 从右向左比较字符(与常规从左向右相反)。
- 利用坏字符规则(Bad Character Rule)和好后缀规则(Good Suffix Rule)跳过不必要的比较,大幅减少匹配次数。
2. 核心规则
(1) 坏字符规则(Bad Character Rule)
- 当发现文本(T)中的某个字符与模式(P)不匹配时:
- 若该字符在模式中不存在,则模式直接跳过该字符。
- 若该字符在模式中存在,则将模式向右移动,对齐模式中该字符的最后出现位置。
(2) 好后缀规则(Good Suffix Rule)
- 当发现不匹配时,若已有部分后缀匹配成功:
- 在模式中查找该后缀的其他出现位置,并移动模式对齐。
- 若不存在,则移动模式到该后缀的后面。
3. 算法步骤
- 预处理:
- 为模式生成坏字符表(记录每个字符最后出现的位置)。
- 生成好后缀表(记录后缀的匹配情况)。
- 匹配:
- 从右向左比较字符,根据规则跳过尽可能多的位置。
4. 示例分析
示例 1:坏字符主导
示例 3:综合应用
5. 算法复杂度
- 文本(T):
ABCABCDAB
- 模式(P):
ABD
- 匹配过程:
-
- 对齐起始位置,从右向左比较:
ABCABCDAB ABD ^ 不匹配(C vs D)
- 坏字符规则:
C
不在模式中,模式右移3位:ABCABCDAB ABD
- 匹配成功(位置4)。
- 对齐起始位置,从右向左比较:
-
示例 2:好后缀主导
- 文本(T):
ABABABAC
- 模式(P):
ABABAC
- 匹配过程:
- 对齐起始位置,从右向左比较:
ABABABAC ABABAC ^ 不匹配(B vs C)
- 好后缀规则:已匹配后缀
ABA
,在模式中查找其前缀ABA
并对齐:ABABABAC ABABAC
- 匹配成功(位置3)。
- 对齐起始位置,从右向左比较:
- 文本(T):
GCTTCTGCTAC
- 模式(P):
TCTG
- 匹配过程:
- 初始对齐:
GCTTCTGCTAC TCTG ^ 不匹配(C vs T)
- 坏字符规则:
C
在模式中最后出现位置是第2位,右移1位:GCTTCTGCTAC TCTG
- 从右向左比较,完全匹配(位置3)。
- 初始对齐:
- 最坏情况:O(n/m)(n为文本长度,m为模式长度)。
- 最佳情况:O(n/m)(如坏字符每次跳过整个模式长度)。
6.C++17 引入的 std::boyer_moore_searcher
如果允许使用 C++17,匹配字符串时候可以使用 std::boyer_moore_searcher
(基于 Boyer-Moore 算法,比 KMP 更快):
#include<bits/stdc++.h>
using namespace std;
int main() {
std::string text = "ABABDABACDABABCABAB";
std::string pattern = "ABABCABAB";
auto searcher = boyer_moore_searcher(pattern.begin(), pattern.end());
auto it = search(text.begin(), text.end(), searcher);
if (it != text.end()) {
cout << "Pattern found at index: " << (it - text.begin()) << endl;
} else {
cout << "Pattern not found." << endl;
}
return 0;
}
7.使用 std::boyer_moore_searcher
找出所有匹配位置
#include <bits/stdc++.h>
using namespace std;
vector<size_t> findAllMatches(const string &text, const string &pattern) {
vector<size_t> matches;
if (pattern.empty()) return matches;
auto searcher = boyer_moore_searcher(pattern.begin(), pattern.end());
auto it = text.begin();
while (it != text.end()) {
auto match = search(it, text.end(), searcher);
if (match == text.end()) break; // 没有更多匹配
size_t pos = match - text.begin();
matches.push_back(pos);
it = match + 1; // 继续搜索下一个可能的匹配
}
return matches;
}
int main() {
string text = "ABABDABACDABABCABAB";
string pattern = "ABAB";
vector<size_t> matches = findAllMatches(text, pattern);
for (size_t pos : matches) {
cout << "Found at index: " << pos << endl;
}
return 0;
}
字符串匹配算法总结:
方法 | 时间复杂度 | 适用场景 |
---|---|---|
std::string::find() | O(n*m) | 简单搜索,适用于短字符串 |
KMP 算法 | O(n+m) | 需要高效匹配时(手动实现) |
std::boyer_moore_searcher (C++17) | O(n/m) | 适用于长字符串,性能最好 |
附加:手动实现boyer_moore算法(非常复杂,容易出错,慎用) :
简化版:
Boyer-Moore 算法简介(简化版)
-
核心思想:从右向左比较模式串(pattern)和文本(text);
-
遇到不匹配:通过“坏字符规则”尽可能多跳;
-
复杂度:最坏 O(nm),但在实践中非常快(接近 O(n));
-
适用于:长文本 + 长模式串。
预处理步骤:构建坏字符表(last occurrence)
#include <iostream>
#include <vector>
#include <string>
#include <unordered_map>
using namespace std;
int boyerMooreSearch(const string& text, const string& pattern) {
int n = text.size();
int m = pattern.size();
if (m == 0) return 0; // 空模式串匹配位置为0
// 构建坏字符规则表:记录每个字符在模式串中的最后出现位置
vector<int> badChar(256, -1); // 支持 ASCII 256 字符
for (int i = 0; i < m; ++i) {
badChar[(unsigned char)pattern[i]] = i;
}
int shift = 0; // 主串中当前匹配窗口的起点
while (shift <= n - m) {
int j = m - 1;
// 从右向左比较
while (j >= 0 && pattern[j] == text[shift + j]) {
--j;
}
// 匹配成功
if (j < 0) {
return shift; // 找到匹配位置
}
// 否则根据坏字符规则移动
char mismatchedChar = text[shift + j];
int lastOccur = badChar[(unsigned char)mismatchedChar];
int move = max(1, j - lastOccur); // 至少移动一位
shift += move;
}
return -1; // 未找到
}
int main() {
string text = "HERE IS A SIMPLE EXAMPLE";
string pattern = "EXAMPLE";
int pos = boyerMooreSearch(text, pattern);
if (pos != -1) {
cout << "Pattern found at position: " << pos << endl;
} else {
cout << "Pattern not found." << endl;
}
return 0;
}
输出:
Pattern found at position: 17
完整版:
完整版原理简述
-
坏字符规则:失配时,将模式串对齐文本中下一个该字符的出现位置。
-
好后缀规则:失配时,尝试对齐前面已匹配的后缀在模式串中其他位置的出现。
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;
class BoyerMoore {
public:
BoyerMoore(const string& pattern) : pat(pattern), m(pattern.length()) {
preprocessBadChar();
preprocessGoodSuffix();
}
int search(const string& text) {
int n = text.length();
int s = 0; // s 是主串的滑动窗口起点
while (s <= n - m) {
int j = m - 1;
// 从右向左匹配
while (j >= 0 && pat[j] == text[s + j]) {
--j;
}
if (j < 0) {
return s; // 匹配成功
} else {
int badCharShift = j - badChar[(unsigned char)text[s + j]];
int goodSuffixShift = goodSuffix[j + 1];
s += max(badCharShift, goodSuffixShift);
}
}
return -1; // 没找到
}
private:
string pat;
int m;
vector<int> badChar; // 坏字符表
vector<int> goodSuffix; // 好后缀表
void preprocessBadChar() {
badChar = vector<int>(256, -1); // 初始化为 -1
for (int i = 0; i < m; ++i) {
badChar[(unsigned char)pat[i]] = i;
}
}
void preprocessGoodSuffix() {
goodSuffix = vector<int>(m + 1, m); // 默认向后移动 m 位
vector<int> suffix(m + 1, -1); // suffix[i]: 长度为 i 的后缀的最右起点
// 计算 suffix 数组
int f = 0, g = m - 1;
suffix[m - 1] = m - 1;
for (int i = m - 2; i >= 0; --i) {
if (i > g && suffix[i + m - 1 - f] < i - g) {
suffix[i] = suffix[i + m - 1 - f];
} else {
if (i < g) g = i;
f = i;
while (g >= 0 && pat[g] == pat[g + m - 1 - f]) {
--g;
}
suffix[i] = f - g;
}
}
// 构建 goodSuffix 表
for (int i = 0; i < m; ++i) {
goodSuffix[i] = m;
}
for (int i = m - 1; i >= 0; --i) {
if (suffix[i] == i + 1) {
for (int j = 0; j < m - 1 - i; ++j) {
if (goodSuffix[j] == m) {
goodSuffix[j] = m - 1 - i;
}
}
}
}
for (int i = 0; i < m - 1; ++i) {
goodSuffix[m - 1 - suffix[i]] = m - 1 - i;
}
goodSuffix[m] = 1; // 避免越界
}
};
int main() {
string text = "abacaabaccabacabaabb";
string pattern = "abacab";
BoyerMoore bm(pattern);
int pos = bm.search(text);
if (pos != -1)
cout << "Pattern found at position: " << pos << endl;
else
cout << "Pattern not found." << endl;
return 0;
}
输出:
Pattern found at position: 10