KMP 算法的核心思想,跟 BM 算法非常相近。在模式串和主串匹配的过程中,把不能匹配的那个字符仍然叫作坏字符,把已经匹配的那段字符串叫作好前缀。
从模式串和主串头部开始匹配,遇到坏字符时模式串向后滑动,可滑动多少呢,这就是KMP算法的关键。好前缀的后缀子串 与 主串的前缀子串 匹配存在时,模式串就不能向后滑动模式串长度个位置了。
为了能够快速得到不匹配是向后滑动的位数,我们先构建了next数组。数组下标是模式串前缀子串结尾字符下标,值是最长可匹配前缀串结尾字符下标。
计算next为了效率使用递归的思想。如果 next[i-1]=k-1,也就是说,子串 b[0, k-1]是 b[0, i-1]的最长可匹配前缀子串。如果子串 b[0, k-1]的下一个字符 b[k],与 b[0, i-1]的下一个字符 b[i]匹配,那子串 b[0, k]就是 b[0, i]的最长可匹配前缀子串。所以,next[i]等于 k。如果 b[0, k-1]的下一字符 b[k]跟 b[0, i-1]的下一个字符 b[i]不相等,我们就可以考察 b[0, i-1]的次长可匹配后缀子串 b[x, i-1]对应的可匹配前缀子串 b[0, i-1-x]的下一个字符 b[i-x]是否等于 b[i]。如果等于,那 b[x, i]就是 b[0, i]的最长可匹配后缀子串。如果不等我们继续找b[0, i-1]的次次长可匹配后缀子串 b[r, i-1]对应的可匹配前缀子串 b[0, i-1-r]的下一个字符 b[i-r]是否等于 b[i]。如果等于,那 b[r, i]就是 b[0, i]的最长可匹配后缀子串。就这样要么找到,要么找不到。
func getNexts(pattern string) []int {
m := len(pattern)
nexts := make([]int, m)
for index := range nexts {
nexts[index] = -1
}
for i := 1; i < m-1; i++ {
j := nexts[i-1]
for pattern[j+1] != pattern[i] && j >= 0 {
j = nexts[j]
}
if pattern[j+1] == pattern[i] {
j += 1
}
nexts[i] = j
}
return nexts
}
func findByKMP(s string, pattern string) int {
n := len(s)
m := len(pattern)
if n < m {
return -1
}
nexts := getNexts(pattern)
j := 0
for i := 0; i < n; i++ {
for j > 0 && s[i] != pattern[j] {
j = nexts[j-1] + 1
}
if s[i] == pattern[j] {
if j == m-1 {
return i - m + 1
}
j += 1
}
}
return -1
}
以上内容摘自《数据结构与算法之美》课程,来学习更多精彩内容吧。