KMP算法用来匹配一个字符串是否包含一个指定字符串;
-
KMP算法的核心是得到子串的部分匹配表;在计算当出现不匹配时子串要回溯的位置时需要用到;
-
部分匹配值:”就是”前缀”和”后缀”的最长的共有元素的长度。
以子串ABCDABD为例,来得到他的部分匹配表
首先弄清楚前缀与后缀的概念;前缀指去除尾部字符的全部组合;后缀指去除首部字符的全部组合;
比如:ABCDA的前缀有[“A”,“AB”,“ABC”,“ABCD”],后缀有[ “BCDA”, “CDA”, “DA”, “A”] -
按照
部分匹配值
的定义计算出ABCDABD的部分匹配表为A B C D A B D 0 0 0 0 1 2 0 - “A”的前缀和后缀都为空集,共有元素的长度为0;
- “AB”的前缀为[A],后缀为[B],共有元素的长度为0;(即
A
和B
;共有元素长度为0) - “ABC”的前缀为[A, AB],后缀为[BC, C],共有元素的长度0;(即
A
B和 BC
;共有元素的长度0) - “ABCD”的前缀为[A, AB, ABC],后缀为[BCD, CD, D],共有元素的长度为0(即
A
BC和 BCD
;共有元素的长度0); - “ABCDA”的前缀为[A, AB, ABC, ABCD],后缀为[BCDA, CDA, DA, A],共有元素为”A”,长度为1(即
A
BCD和 BCDA
;共有元素的长度1); - “ABCDAB”的前缀为[A, AB, ABC, ABCD, ABCDA],后缀为[BCDAB, CDAB, DAB, AB, B],共有元素为”AB”,长度为2(即
AB
CDA和 BCDAB
;共有元素的长度2); - “ABCDABD”的前缀为[A, AB, ABC, ABCD, ABCDA, ABCDAB],后缀为[BCDABD, CDABD, DABD, ABD, BD, D],共有元素的长度为0。(即
A
BCDAB和 BCDABD
;共有元素的长度0)
- 根据公式:字串需要回溯的长度=匹配的字符长度-最后一个匹配字符对应的”部分匹配值”
从而得到需要回溯到的位置索引//go代码实现返回他的Next切片 func Next(needle string) []int { l := len(needle) next := make([]int, l) next[0] = -1 k := -1 i := 0 for i < l-1 { if k == -1 || needle[k] == needle[i] { i++ k++ next[i] = k } else { k = next[k] } } return next }
举例:
假定求字符串:BBCABCDABABCDABCDABDE(母串)中是否包含ABCDABD(子串)
在开始计算之前需要得到子串的部分匹配值
-
从符串BBCABCDABABCDABCDABDE第一位开始匹配ABCDABD第一位;符合的话就匹配ABCDABD的第二位,以此类推
-
按照第1步逻辑我们走到母串的索引为3的位置;BBCABCDABABCDABCDABDE;发现最后的A与字串最后的D不符合;按照暴力匹配来算,可能得从母串的索引为4的位置再重复第1步;但是按照KMP算法的子串部分匹配值;用匹配的字符长度-最后一个匹配字符对应的”部分匹配值”得到字串需要回溯的长度;
回溯的长度=6-2;指针指到C从C开始匹配
-
C与索引9的A不匹配;还需要回溯:2-0=2;回到字串的第一位A;
-
A与母串的索引9相同;重复第一步;
-
以此循环往复;最终走到母串的索引19;BBCABCDABABCDABCDABDE;完成匹配;这时候也可以把字串给回溯7-0位重新开始重复第1步;试图寻找是否还存在匹配
根据Next返回的切片计算出字符串匹配的位置
//KMP字符串匹配
func SindexKMP(S,T string) int {
next:=NextArray(T)
i := 0
j := 0
//同时满足才可以 找除字符串出现的第一个位置
for ;i <= len(S) -1 && j <= len(T) -1;{
if j == -1|| S[i] == T[j]{
//当字符匹配时 i j 都加1
i++
j++
}else{
//子串的 偏移量 从next数组中取 i 不变
j = next[j]
}
}
//如果 j 大于 或者 等于 T串的长度 说明匹配成功
if j >= len(T) -1 {
return i - len(T) +1
}
return 0
}
调用:
func main(){
str:="ABCDABD"
array := Next(str)
fmt.Println(array)
T:="BBCABCDABABCDABCDABDE"
kmp := SindexKMP(T, str)
fmt.Println(kmp)
fmt.Println(T[kmp-1:kmp+6] )
}
/*
output:
[-1 0 0 0 0 1 2]
14
ABCDABD
*/