BM算法

BM 算法包含两部分,分别是坏字符规则(bad character rule)和好后缀规则(good suffix shift)。

  1. 坏字符规则
    BM 算法的匹配顺序是按照模式串下标从大到小的顺序,倒着匹配的。

我们从模式串的末尾往前倒着匹配,当我们发现某个字符没法匹配的时候。我们把这个没有匹配的字符叫作坏字符(主串中的字符)。
在这里插入图片描述我们拿坏字符 c 在模式串中查找,发现模式串中并不存在这个字符,也就是说,字符 c 与模式串中的任何字符都不可能匹配。这个时候,我们可以将模式串直接往后滑动三位,将模式串滑动到 c 后面的位置,再从模式串的末尾字符开始比较。
在这里插入图片描述这个时候,坏字符 a 在模式串中是存在的,模式串中下标是 0 的位置也是字符 a。这种情况下,我们可以将模式串往后滑动两位,让两个 a 上下对齐,然后再从模式串的末尾字符开始,重新匹配。
在这里插入图片描述当发生不匹配的时候,我们把坏字符对应的模式串中的字符下标记作 si。如果坏字符在模式串中存在,我们把这个坏字符在模式串中的下标记作 xi。如果不存在,我们把 xi 记作 -1。那模式串往后移动的位数就等于 si-xi。(注意,我这里说的下标,都是字符在模式串的下标)。

  1. 好后缀规则
    在这里插入图片描述我们把已经匹配的 bc 叫作好后缀,记作{u}。我们拿它在模式串中查找,如果找到了另一个跟{u}相匹配的子串{u*},那我们就将模式串滑动到子串{u*}与主串中{u}对齐的位置。
    在这里插入图片描述当模式串滑动到前缀与主串中{u}的后缀有部分重合的时候,并且重合的部分相等的时候,也有可能会存在完全匹配的情况。
    在这里插入图片描述下面我们来看BM算法的实现:
    为了能够快速找到坏字符在模式串中的位置,我们可以将模式串中的每个字符及其下标都存到散列表中。
    在这里插入图片描述为了能够快速得到好后缀规则中后滑位数,我们将模式串的后缀子串在模式串中存在另一个匹配串的最后一个字符的位置记录在suffix数组中,下标是模式串中后缀子串的长度,值是模式串中另一个与后缀串匹配串的最后一个字符的位置。除此我们还将模式串的前缀串存在另一个匹配串的数据存在prefix数组中,下标是模式串中前缀串的长度,值是 是否存在另一个与前缀串匹配的串。
    在这里插入图片描述下面上代码,代码看起来可能有点绕,难理解。上面思路清晰了再去看代码比较好,重点是bc、suffix、prefix这三个数组的生成和使用,贯穿了整个BM算法的核心。
//bc: pattern char index hash mapping
func generateBC(pattern string) []int {

	bc := make([]int, 256)

	for index := range bc {
		bc[index] = -1
	}

	for index, char := range pattern {
		bc[int(char)] = index
	}

	return bc
}

//generate suffix and prefix array for pattern
func generateGS(pattern string) ([]int, []bool) {
	m := len(pattern)
	suffix := make([]int, m)
	prefix := make([]bool, m)

	//init
	for i := 0; i < m; i++ {
		suffix[i] = -1
		prefix[i] = false
	}

	for i := 0; i < m-1; i++ {
		j := i
		k := 0
		for j >= 0 && pattern[j] == pattern[m-1-k] {
			j--
			k++
			suffix[k] = j + 1
		}

		if j == -1 {
			prefix[k] = true
		}
	}

	return suffix, prefix
}

//todo
func moveByGS(patternLength int, badCharStartIndex int, suffix []int, prefix []bool) int {

	//length of good suffix
	k := patternLength - badCharStartIndex - 1

	//complete match
	if suffix[k] != -1 {
		return badCharStartIndex + 1 - suffix[k]
	}

	//partial match
	for t := patternLength - 1; t > badCharStartIndex+1; t-- {
		if prefix[t] {
			return t
		}
	}

	//no match
	return patternLength

}

func bmSearch(main string, pattern string) int {
	//defensive
	if len(main) == 0 || len(pattern) == 0 || len(pattern) > len(main) {
		return -1
	}

	bc := generateBC(pattern)
	suffix, prefix := generateGS(pattern)

	n := len(main)
	m := len(pattern)

	// i : start index of main string
	step := 1
	for i := 0; i <= n-m; i = i + step {
		subStr := main[i : i+m]
		k, j := findBadChar(subStr, pattern, bc)

		stepForBC := j - k
		//j is bad char occur index
		if j == -1 {
			return i
		}

		stepForGS := -1
		if j < m-1 {
			stepForGS = moveByGS(m, j, suffix, prefix)
		}

		//k is bad char index in pattern
		step = int(math.Max(float64(stepForBC), float64(stepForGS)))
	}

	return -1
}

func findBadChar(subStr string, pattern string, bc []int) (int, int) {

	j := -1
	k := -1
	badChar := rune(0)

	for index := len(subStr) - 1; index >= 0; index-- {
		if subStr[index] != pattern[index] {
			j = index
			badChar = rune(subStr[index])
			break
		}
	}

	//if bad character exist, then find it's index at pattern
	if j > 0 {
		k = bc[int(badChar)]
	}

	return k, j
}

以上内容摘自《数据结构与算法之美》课程,来学习更多精彩内容吧。

在这里插入图片描述

### Boyer-Moore 算法的实现及原理 #### 1. 基本概念 Boyer-Moore (BM) 算法是一种高效的字符串匹配算法,其核心在于通过从模式串的 **末尾** 开始比较,并结合两种规则——**坏字符规则** 和 **好后缀规则** 来加速匹配过程[^2]。 #### 2. 时间复杂度分析 该算法的最佳情况下的时间复杂度为 \(O(n/m)\),其中 \(n\) 表示文本串长度,\(m\) 表示模式串长度;而在最坏的情况下,时间复杂度可能达到 \(O(n \cdot m)\)[^2]。尽管如此,在实际应用中 BM 算法通常表现得非常高效。 #### 3. 核心规则详解 ##### (1)坏字符规则 当发现不匹配时,“坏字符”是指当前正在比较但未成功匹配的那个字符。根据此规则,模式串会向右移动到尽可能远的位置,使得模式串中的某个字符能够与文本串中的“坏字符”对齐。 ##### (2)好后缀规则 如果部分匹配失败,则考虑已经匹配的部分作为“好后缀”。基于已知的好后缀位置关系,决定如何调整模式串以继续下一次尝试。 #### 4. Python 实现代码 以下是使用 Python 编写的简化版 Boyer-Moore 字符串匹配函数: ```python def boyer_moore(text, pattern): def build_bad_char_shift(pattern): bad_char = {} for i in range(len(pattern)-1): bad_char[pattern[i]] = i return bad_char def find_good_suffix_shift(pattern): length = len(pattern) suffix = [-1]*length prefix_set = set() for i in reversed(range(length)): prefix_set.add(pattern[i:]) j = i + 1 while j < length and not pattern[j:] in prefix_set: suffix[j] = i j += 1 good_suffix_shift = [0]*(len(suffix)+1) for i in range(len(suffix)): if suffix[i]!=-1: good_suffix_shift[len(pattern)-i]=len(pattern)-suffix[i]-1 return good_suffix_shift bad_char_table = build_bad_char_shift(pattern) good_suffix_table = find_good_suffix_shift(pattern) s = 0 # Shift of the pattern with respect to text while s <= len(text) - len(pattern): shift = 1 mismatched = False for i in range(len(pattern)-1,-1,-1): if text[s+i] != pattern[i]: char_shift = max(1,i-bad_char_table.get(text[s+i],-1)) suffix_shift = good_suffix_table[len(pattern)-(len(pattern)-i)] shift = max(shift,char_shift,suffix_shift) mismatched=True break if not mismatched: return s s += shift return -1 ``` 上述代码实现了完整的 Boyer-Moore 搜索逻辑,包括构建必要的辅助表以及执行具体的匹配操作。 #### 5. 应用场景扩展 除了传统的字符串匹配外,Boyer-Moore 还被应用于更广泛的领域,比如网络安全中的恶意代码检测和防火墙规则匹配等场合[^1]。它凭借出色的性能成为这些高需求环境的理想选择之一。 ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值