理解KMP花了好久的时间,参考了很多资料,在网络上找到一篇很好的讲解来自这里。
理解KMP第一点是,要理解它是通过什么样的方式来达到比暴力匹配更高效的速率。很简单就是减少无谓的比较次数。比如
S串: abaabaabc 和 T串: abaabc 进行比较,按照普通的比较方法是
abaabaabc abaabaabc abaabaabc abaabaabc
abaabc abaabc abaabc abaabc 按照这种方式一个个的比较过去但是仔细观察可以发现有些比较是没有必要的。
比如T串C的前面有两个ab和S串的ab比较过了,那么其实这中间一步步的比较过程是可以直接省略的直接到达最后一步的比较也就是
abaabaabc
abaabc
原因如下,
首先T串c的前缀ab与后缀ab是相等的,换句话说后缀ab比较过的内容,循环到前缀ab时是不需要在比较的。也就是说,在第一次c与a失配时,可以直接将前缀ab的后一个字符与之前c失配得字符a进行比较。
当然有人会觉得这么比较当中会漏掉很多字符,万一出现个可以匹配的却漏了过去呢。这里我会证明这种情况不会发生。因为我举得例子中'ab'是S串中c字符前的子串中的最大前后缀(好好理解这句话的意思)。比如 abcabe(最大前后缀的值为2,e字符前的最大前后缀为ab), abcdabce(3,abc),这里一定要好好理解好。
所以ab是S串中c字符之前的最大前后缀,在S串中c字符之前的两个ab中间无论出现什么都不可能和T串完成匹配。比如两个ab中间出现的字符都和ab不相等那么肯定不能完成匹配,又或者即使两个ab中间又出现了ab,比如 T串中是 ab......省略,用1代表......ab......省略,用2代表......ab 这种情况,也是没有关系的。如果第一个ab想要和T串中间的ab完成匹配,因为之前已经匹配到了c字符,也就是第三个ab之后的那个c字符,也就是意味着之前的所有字符都是匹配的。而如果S串中的第一个ab和T串中的第二个ab开始匹配,那么首先要满足的条件就是S串中1,2两部分必须是一样的(其实也就是T串中的1,2两部分,但是刚才提到了完全匹配,所以是一样的。),但是这两部分不能是一样的,因为如果是一样的那么,S串中的ab就不是最大前后缀了,举个例子是:abcccabcccab 这个例子的最大前后缀是abcccab,举任何例子都是一样的结果。所以通过这一段一系列的例证,现在可以说,当第一次c与a失陪时,是可以直接将S串前缀ab的后一个字符直接与a比较。
而我们可以将所有的这种情况通过计算存储到一个数组里面,就像刚才的例子,在c与a失配之后,直接将S串中索引为2的字符,直接与当前T串所在的索引下标匹配。这样就可以减少许许多多不必要的无谓的比较。
而这个数组,叫做next数组。(这种算法用言语描述真的好累啊。。。。。。。)
package main
import (
"fmt"
"time"
)
func main() {
var sss string = ""
for i:=0; i<9999; i++ {
sss +="abcdefg"
}
sss += "abcdk"
var trs []rune = []rune(sss)
t := time.Now()
fmt.Println(findString2(string(trs), "abcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdk"))
fmt.Println(time.Now().Sub(t))
t = time.Now()
fmt.Println(findStringKMP(string(trs), "abcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdk"))
fmt.Println(time.Now().Sub(t))
}
/*
true
225.949046ms
true
98.827263ms
*/
/*
......d.......
......e.......
*/
func getNext(str string) []int{
var runes []rune = []rune(str)
var length int = len(runes)
var i, j int = 0, -1//i代表模板字符串下标, j代表最大前缀长度-1
var next []int = make([]int, length)
next[0] = -1
for i < length - 1 {
if j == -1 || runes[j] == runes[i] { //next[i],next[j]比较的是前后缀的单个字符是否相等
i ++
j ++
if runes[j] == runes[i] {
next[i] = next[j]
} else {
next[i] = j
}
} else {
j = next[j] // 如果字符不同,则J值回朔
}
}
fmt.Println(next)
return next
}
func findStringKMP(src, des string) bool {
next := getNext(des)
var runSrc = []rune(src)
var runDes = []rune(des)
var i, j int = 0, 0
for i < len(src) && j < len(des) {
if j == -1 || runSrc[i] == runDes[j] {
i ++
j ++
} else {
j = next[j]
}
}
if j == len(des) {
return true
} else {
return false
}
}
func findString(src, des string) bool {
var srcRs []rune = []rune(src)
var desRs []rune = []rune(des)
var count int = 0
for i := 0; i < len(srcRs); i++ {
tmp := i
for j := 0; j <= len(desRs); j++ {
count ++
if j == len(desRs) {
return true
}
if tmp == len(srcRs) {
return false
}
if desRs[j] == srcRs[tmp] {
tmp++
continue
} else {
break
}
}
}
return false
}
func findString2(src, des string) bool {
var srcRs []rune = []rune(src)
var desRs []rune = []rune(des)
var i, j int
for i < len(srcRs) && j < len(desRs) {
if srcRs[i] == desRs[j] {
i ++
j ++
} else {
i = i - j + 1
j = 0
}
}
if j == len(desRs) {
return true
}
return false
}