BM 算法包含两部分,分别是坏字符规则(bad character rule)和好后缀规则(good suffix shift)。
- 坏字符规则
BM 算法的匹配顺序是按照模式串下标从大到小的顺序,倒着匹配的。
我们从模式串的末尾往前倒着匹配,当我们发现某个字符没法匹配的时候。我们把这个没有匹配的字符叫作坏字符(主串中的字符)。
我们拿坏字符 c 在模式串中查找,发现模式串中并不存在这个字符,也就是说,字符 c 与模式串中的任何字符都不可能匹配。这个时候,我们可以将模式串直接往后滑动三位,将模式串滑动到 c 后面的位置,再从模式串的末尾字符开始比较。
这个时候,坏字符 a 在模式串中是存在的,模式串中下标是 0 的位置也是字符 a。这种情况下,我们可以将模式串往后滑动两位,让两个 a 上下对齐,然后再从模式串的末尾字符开始,重新匹配。
当发生不匹配的时候,我们把坏字符对应的模式串中的字符下标记作 si。如果坏字符在模式串中存在,我们把这个坏字符在模式串中的下标记作 xi。如果不存在,我们把 xi 记作 -1。那模式串往后移动的位数就等于 si-xi。(注意,我这里说的下标,都是字符在模式串的下标)。
- 好后缀规则
我们把已经匹配的 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
}
以上内容摘自《数据结构与算法之美》课程,来学习更多精彩内容吧。