元素码农
基础
UML建模
数据结构
算法
设计模式
网络
TCP/IP协议
HTTPS安全机制
WebSocket实时通信
数据库
sqlite
postgresql
clickhouse
后端
rust
go
java
php
mysql
redis
mongodb
etcd
nats
zincsearch
前端
浏览器
javascript
typescript
vue3
react
游戏
unity
unreal
C++
C#
Lua
App
android
ios
flutter
react-native
安全
Web安全
测试
软件测试
自动化测试 - Playwright
人工智能
Python
langChain
langGraph
运维
linux
docker
工具
git
svn
🌞
🌙
目录
▶
算法基础
▶
复杂度分析
时间复杂度
空间复杂度
渐进符号
▶
算法思想
分治法
贪心算法
回溯法
概率算法
枚举算法
递推算法
递归算法
动态规划
分支限界法
模拟算法
▶
算法分析
正确性证明
性能优化
算法比较
▶
搜索算法
▶
基本搜索
顺序搜索
二分搜索
插值搜索
▶
图搜索
深度优先搜索
广度优先搜索
启发式搜索
▶
高级搜索
双向搜索
A*算法
模式匹配
▶
排序算法
▶
比较类排序
冒泡排序
快速排序
归并排序
插入排序
选择排序
▶
非比较类排序
计数排序
基数排序
桶排序
▶
高级排序
堆排序
希尔排序
外部排序
拓扑排序
▶
图论算法
▶
图的表示
邻接矩阵
邻接表
边集数组
▶
最短路径
Dijkstra算法
Floyd算法
Bellman-Ford算法
最短路径概述
▶
生成树
Prim算法
Kruskal算法
并查集
最小生成树
▶
图的连通性
强连通分量
割点与桥
双连通分量
欧拉路径
▶
动态规划
▶
基础概念
最优子结构
重叠子问题
状态转移方程
▶
经典问题
背包问题
最长公共子序列
编辑距离
▶
优化技巧
空间优化
状态压缩
区间动态规划
▶
字符串算法
▶
字符串匹配
KMP算法
Boyer-Moore算法
Sunday算法
▶
字符串处理
字典树
后缀数组
字符串哈希
▶
压缩算法
游程编码
Huffman编码
LZ77算法
发布时间:
2025-03-21 20:18
↑
☰
# Boyer-Moore字符串匹配算法 Boyer-Moore算法是一种高效的字符串匹配算法,由Robert S. Boyer和J Strother Moore于1977年发明。它通过从右向左扫描模式串,并利用两个启发式规则来实现快速跳转,在实践中表现出优异的性能。 ## 基本概念 ### 匹配原理 Boyer-Moore算法的核心思想包含两个主要规则: 1. 坏字符规则(Bad Character Rule) - 当发生不匹配时,将模式串向右移动,使不匹配字符与模式串中最右边的相同字符对齐 - 如果不匹配字符在模式串中不存在,则移动整个模式串长度 2. 好后缀规则(Good Suffix Rule) - 当发生不匹配时,移动模式串使已匹配的后缀与模式串中另一个相同的后缀对齐 - 如果没有相同的后缀,则移动模式串到下一个可能的位置 ## 实现原理 ### 预处理 ```go // 构建坏字符规则表 func buildBadCharTable(pattern string) map[byte]int { bc := make(map[byte]int) length := len(pattern) // 默认移动整个模式串长度 for i := 0; i < 256; i++ { bc[byte(i)] = length } // 更新模式串中字符的最右位置 for i := 0; i < length-1; i++ { bc[pattern[i]] = length - 1 - i } return bc } // 构建好后缀规则表 func buildGoodSuffixTable(pattern string) []int { length := len(pattern) gs := make([]int, length) lastPrefixPosition := length // 初始化好后缀表 for i := length - 1; i >= 0; i-- { if isPrefix(pattern, i+1) { lastPrefixPosition = i + 1 } gs[length-1-i] = lastPrefixPosition - i + length - 1 } // 处理重叠情况 for i := 0; i < length-1; i++ { slen := suffixLength(pattern, i) gs[slen] = length - 1 - i + slen } return gs } // 判断是否是前缀 func isPrefix(pattern string, p int) bool { j := 0 for i := p; i < len(pattern); i++ { if pattern[i] != pattern[j] { return false } j++ } return true } // 计算后缀长度 func suffixLength(pattern string, p int) int { length := 0 j := len(pattern) - 1 i := p for i >= 0 && pattern[i] == pattern[j] { length++ i-- j-- } return length } ``` ### 基本实现 ```go // Boyer-Moore算法实现 func BoyerMoore(text, pattern string) int { if len(pattern) == 0 { return 0 } if len(text) < len(pattern) { return -1 } // 构建预处理表 bc := buildBadCharTable(pattern) gs := buildGoodSuffixTable(pattern) textLen := len(text) patternLen := len(pattern) pos := 0 for pos <= textLen - patternLen { j := patternLen - 1 // 从右向左比较 for j >= 0 && pattern[j] == text[pos+j] { j-- } // 找到匹配 if j < 0 { return pos } // 计算移动距离 bcShift := bc[text[pos+j]] - patternLen + 1 + j gsShift := gs[j] // 选择最大的移动距离 pos += max(bcShift, gsShift) } return -1 } ``` ### 优化实现 ```go // Boyer-Moore匹配器 type BMatcher struct { pattern string bc map[byte]int gs []int patternLen int } // 创建匹配器 func NewBMatcher(pattern string) *BMatcher { return &BMatcher{ pattern: pattern, bc: buildBadCharTable(pattern), gs: buildGoodSuffixTable(pattern), patternLen: len(pattern), } } // 查找所有匹配位置 func (bm *BMatcher) FindAll(text string) []int { result := make([]int, 0) textLen := len(text) pos := 0 for pos <= textLen - bm.patternLen { j := bm.patternLen - 1 // 从右向左比较 for j >= 0 && bm.pattern[j] == text[pos+j] { j-- } // 找到匹配 if j < 0 { result = append(result, pos) pos += bm.gs[0] // 移动到下一个可能的位置 } else { // 计算移动距离 bcShift := bm.bc[text[pos+j]] - bm.patternLen + 1 + j gsShift := bm.gs[j] pos += max(bcShift, gsShift) } } return result } ``` ## 应用场景 ### 文本搜索引擎 ```go // 简单的文本搜索引擎 type TextSearchEngine struct { matcher *BMatcher index map[string][]int } // 建立文档索引 func (tse *TextSearchEngine) IndexDocument(docId string, content string) { words := strings.Fields(content) tse.index[docId] = make([]int, 0) for _, word := range words { tse.matcher = NewBMatcher(word) if positions := tse.matcher.FindAll(content); len(positions) > 0 { tse.index[docId] = append(tse.index[docId], positions...) } } } // 搜索文档 func (tse *TextSearchEngine) Search(query string) map[string][]int { results := make(map[string][]int) tse.matcher = NewBMatcher(query) for docId, _ := range tse.index { if positions := tse.matcher.FindAll(docId); len(positions) > 0 { results[docId] = positions } } return results } ``` ### DNA序列匹配 ```go // DNA序列匹配器 type DNAMatcher struct { matcher *BMatcher cache map[string][]int } // 在DNA序列中查找特定模式 func (dm *DNAMatcher) FindPattern(sequence, pattern string) []int { // 检查缓存 if positions, exists := dm.cache[pattern]; exists { return positions } // 创建新的匹配器 dm.matcher = NewBMatcher(pattern) positions := dm.matcher.FindAll(sequence) // 缓存结果 dm.cache[pattern] = positions return positions } // 分析DNA序列 func (dm *DNAMatcher) AnalyzeSequence(sequence string, patterns []string) map[string]int { results := make(map[string]int) for _, pattern := range patterns { matches := dm.FindPattern(sequence, pattern) results[pattern] = len(matches) } return results } ``` ## 性能分析 ### 时间复杂度 1. 预处理阶段 - 坏字符规则:O(m + σ),m为模式串长度,σ为字符集大小 - 好后缀规则:O(m) 2. 匹配阶段 - 最好情况:O(n/m),n为文本串长度 - 平均情况:O(n) - 最坏情况:O(n*m) ### 空间复杂度 1. 预处理空间 - 坏字符表:O(σ) - 好后缀表:O(m) 2. 匹配空间 - 基本实现:O(1) - 查找所有匹配:O(n) ### 性能对比 1. 与其他算法比较 - 比KMP算法实现更复杂 - 在实际应用中通常比KMP更快 - 特别适合长模式串和大字符集 2. 优势 - 从右向左扫描更容易发现不匹配 - 利用两个规则实现更大的跳转 - 实际应用中表现优异 ## 实践建议 ### 使用场景选择 1. 适合场景 - 文本搜索引擎 - DNA序列匹配 - 大规模文本处理 - 模式串较长的情况 2. 不适合场景 - 短文本或短模式串 - 二进制数据匹配 - 需要频繁更新模式串 ### 优化技巧 1. 实现优化 - 预处理表缓存 - 特殊情况快速返回 - 使用更紧凑的数据结构 2. 应用优化 - 批量处理文本 - 并行处理大文件 - 结合其他算法 ## 总结 1. 优点 - 实际应用中性能优异 - 适合长模式串匹配 - 可以处理大字符集 2. 缺点 - 实现相对复杂 - 预处理开销较大 - 需要额外空间存储预处理信息 3. 使用建议 - 根据具体场景选择实现方式 - 注意预处理表的构建和维护 - 考虑与其他算法配合使用