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
}

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

在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值