茶艺师学算法打卡12:BF/RK字符串匹配算法

106 阅读2分钟

茶艺师学算法打卡12:BF/RK字符串匹配算法

学习笔记

BF算法

全名为 Brute Force ,暴力匹配算法。
简直简单粗暴,主串长度为 n ,模式串长度为 m ,就直接硬来比较 nm+1n - m + 1 次,直到找到匹配的部分为止。
时间复杂度自然是 O(nm)O(n * m) ,但别嫌弃它耗时久,它不仅简单好用(好写),在小规模的字符串匹配中,BF 算法比别的高级匹配算法表现还好。

RK算法

全名为 Rabin Karp 匹配算法,就是 Rabin 与 Karp 这两人一起发明的算法。
这算法的一句话总结,就是 BF 算法 + 哈希表结合。
这里体现为两点。
一,在比较 nm+1n - m + 1 次主串子串与模式串时,通过哈希算法,把两者都换算为“哈希值”,再比较两“哈希值”是否相等。

二、这 nm+1n - m + 1 次主串子串可以一次算完,用一个数组将其系列结果存起来,到时要比较,直接通过下标找出算好的答案,这分明就是哈希表的应用。
这里附上简单的代码示意(主体思路一致,但在细节处理上更偏向我的习惯)

// 主串 "Hello"
// 模式串 "ll"

n, m := 5, 2

s1 = " " + "Hello"
s2 = " " + "ll"

s1Hash, p131 := make([]int64, n + 1), make([]int64, m + 1)  
s1Hash[0], p131[0] = 0, 1 
var s2Hash int64

// 计算主串的哈希值
for i := 1; i <= n; i++ {
    s1Hash[i] = s1Hash[i - 1] * 131 + int64(s1[i]) - 'a' + 1
    p131[i] = p131[i - 1] * 131
}

// 计算模式串的哈希值
for i := 1; i <= m; i++ {
  s2Hash = s2Hash * 131 + int64(s2[i]) - 'a' + 1
}

for i := m; i <= n; i++ {
    if cal(s1Hash, p131, i - m + 1, i) == s2Hash {
        // 找到匹配
        // 当然可以再次验证两个子窜是否相等
    }
}

// 快速计算任一子串的哈希值
func cal(s1Hash, p131 []int64, i, j int) int64 {
    return s1Hash[j] - s1Hash[i - 1] * p131[j - i + 1]
}

时间复杂度为 O(n)O(n) ,基本就是算主串的哈希值耗时。
当然,如果哈希函数设计得不好,经常引发哈希碰撞,RK 算法的耗时还是会退化为 O(nm)O(n * m) ,也就是 BF 算法的水平了。