KMP - 字符串匹配算法
前言:其实本来没有打算写KMP算法,毕竟这种课本上的基础算法,想必大家也都了解。但是某夜刷题遇到时竟苦苦思索不得正解,网上文章又大多杂乱无章,或代码不得正解,故写下此篇以便来日复习
代码先行
算法就是拿来用的,没有代码的算法算什么
以下是用Go语言实现的KMP算法
func kmp(haystack string, needle string) int {
//when needle is empty,return 0 is available
if len(needle) == 0 {
return 0
}
next := getNext(needle)
i := 0
j := 0
for i < len(haystack) && j < len(needle) {
if j == -1 || haystack[i] == needle[j] {
i++
j++
} else {
j = next[j]
}
}
if j == len(needle) {
return i - j
}
return -1
}
//original getNext function
func getNext(str string) []int {
var next = make([]int, len(str))
next[0] = -1
i := 0
j := -1
for i < len(str) - 1 {
if j == -1 || str[i] == str[j] {
i++
j++
next[i] = j
} else {
j = next[j]
}
}
return next
}
//better getNext function
func getNext(str string) []int {
var next = make([]int, len(str))
next[0] = -1
i := 0
j := -1
for i < len(str) - 1 {
if j == -1 || str[i] == str[j] {
i++
j++
if str[i] == str[j] {
next[i] = next[j]
} else {
next[i] = j
}
} else {
j = next[j]
}
}
return next
}
- 需要注意的是这里有两个getNext函数,其中一个是前者的改良版
- 其他语言写法一样,稍稍改变即可
边做边学
- 现在,给你一道算法题,
给出两个字符串A和B,问,A中是否存在B,如果存在输出起始位置,不存在则输出-1
- 对于大多数高级语言来说,它们已经提供了很多方法为我们省去了这些繁琐的过程,如Java的indexOf、PHP的strstr
- 或者就算不依赖于这些,我们也可以采用暴力扫描法,当时,其中的
时间复杂度
可想而知
func strstr(haystack string, needle string) int {
i := 0
j := 0
for i < len(haystack) && j < len(needle) {
if haystack[i] == needle[j] {
i++
j++
} else {
//move one step
i = i - j + 1
j = 0
}
}
if j == len(needle) {
return i - j
}
return -1
}
- 暴力破解法的特点在于,两者字符串发生不匹配时,选择是将主串指针向前移动一位,并重置匹配串指针,KMP的巧妙之处正在于此,通过移动匹配串来快速找到匹配位置
走进核心
至少至此,我们至少知道了KMP为何而来,以及它解决了什么, 但是,
KMP的next数组到底是什么?
- PMT:Partial Match Table,部分匹配表,即我们在KMP算法中,去计算得出的next数组
- 以字符串 ababcabef 为例
- 在这里我们需要了解,字符串的
前缀
和后缀
,这个好理解,对于hello来说,它的前缀包括了{“h”,“he”,“hel”,“hell”},它的后缀包括了{“o”,“lo”,“llo”,“ello”} - 对于PMT来说,它的
value是由前缀集合和后缀集合中,最长的交集字符串长度
,如对aba来说最长交集字符串自然是a,因此index=2的value=1 - next数组就是在此的基础上,
除去当前字符
的最长前缀集合和后缀集合交集的最长字符串长度,其实看数据也可以发现,next的值相较于value整体右移了一个单位,并将next[0]赋值为-1
图解搜索
- 现在,我们来尝试在 该字符串 abcababc 中去搜索 ababc,这里的next数组根据上表可以看出是next={-1,0,0,1,2}
- 我们这时候根据next数组,将next[j]赋值给j,即j=0
- 这时候仍然不匹配,根据next数组j=-1,则i和j都加一
- 最终匹配到了搜索串末尾
- 最终 ,
我们找到的位置就是 i - j
KMP优化
- 在计算next数组时,如果a字符与它next值指向的b字符相等,则a字符的next值指向b字符的next值
- 如对于ABABC来说,index=3时,next值应该等于1,优化后指向0
- 提高了KMP的效率