1.前言
Kmp是一种用于字符串比较的常用算法,那KMP到底是如何实现快速匹配字符串的呢?下面由一个具体的例子引入
这个例子中文本串是haystack = "sadbutsad",目标串是:needle = "sad"
其解法我们首先想到的就是暴力破解
暴力解法的流程图如上图所示,从文本串的第一位开始和目标串一一比较字符,不匹配则从文本串后的下一位开始比较,直到i=len(文本串)-len(目标串)
暴力破解代码如下:
func strStr(haystack string, needle string) int {
if len(needle)>len(haystack){
return -1
}
for i:=0;i<len(haystack)-len(needle);i++{
flage:=true
for j:=0;j<len(needle);j++{
if haystack[i+j]!=needle[j]{
flage=false
break
}
}
if flage{
return i
}
}
return -1
}
2.理论
Kmp主要的思想是匹配字符串的时候,出现不匹配的情况,记录目标串已经匹配的一部分内容,利用这些信息避免从头匹配目标串
如上图所示,当匹配到f时,发现与b不匹配,这时候kmp算法回退到b这个位置,从这个位置往后与文本串的冲突位置b往后一一匹配。那么回退的位置b是如何确定的呢?下面就引入这样几个概念---前缀表,前缀,后缀,最长前后缀
2.1前缀表
前缀表是用来回退的,他记录了目标串与文本串不匹配的时候,目标串应该从哪个位置重新开始匹配。
在具体的代码里很多人用next数组来表示。
2.2前缀
前缀:包含字符串第一个字符,不包含字符串最后一个字符的子串
2.3后缀
后缀:包含字符串最后一个字符,不包含字符串第一个字符的子串
2.4最长相等前后缀
最长相等前后缀:相等的前缀后缀且长度最长的子串
如下图所示,对aabaaf的每个子串求取最长相等前后缀,最长相等前后缀的长度就是前缀表的数值,没有最长相等前后缀数值为0
2.5根据前缀表进行目标串回退
上面得出aabaaf的前缀表是010120,发生冲突的位置在f,kmp会找到冲突位置的前一位的前缀表,其数值就是目标串要回退到的位置。
获取前缀表的函数中具体做一下三个操作:
1.初始化
2.处理前后缀不相等
3.处理前后缀相等
初始化:
KMP设定两个指针 i 和 j,那么这两个指针代表着什么含义呢?
j表示前缀的结束位置,也代表着i之前子串最长相等前后缀的长度,开始位置从0开始;
i表示后缀的结束位置,因为后缀不包括首个字母所以i的位置从1开始
又因为第一个单字符子串a是只有一个字符的字符串没有前后缀,最长相等前后缀长度必然为0所以设置next[0]=0
处理前后缀不相等
needle[i]!=needle[j],j要循环回退,j--;并且j>0否则数组会越界,如果j回退0
next[i]=j
处理前后缀相等
needle[i]==needle[j],j++,next[i]=j
具体流程如下图
获取前缀表代码实现
//求取前缀表
func GetPrefix(next []int,needle string){
j:=0 //j表示前缀的结束位置,开始位置从0开始;j也代表着i之前子串最长相等前后缀的长度
next[0]=0//前缀表第一个元素为0
for i:=1;i<len(needle);i++{
//前后缀不相等
for needle[i]!=needle[j]&&j>0{
j=next[j-1]
}
if needle[i]==needle[j]{
j++
}
next[i]=j
}
}
3.KMP解决字符串匹配问题
kmp解决Leetcode算法题第28题
func strStr(haystack string, needle string) int {
next:=make([]int,len(needle))
GetPrefix(next,needle)
j:=0//目标串比较的起始位置
for i:=0;i<len(haystack);i++{
//目标串与文本串字符不相等
for haystack[i]!=needle[j]&&j>0{
j=next[j-1]
}
if haystack[i]==needle[j]{
j++
}
//neddle遍历到最后一位时匹配到字符串
if j==len(needle){
return i-len(needle)+1
}
}
return -1
}
//求取前缀表
func GetPrefix(next []int,needle string){
j:=0 //j表示前缀的结束位置,开始位置从0开始;j也代表着i之前子串最长相等前后缀的长度
next[0]=0//前缀表第一个元素为0
for i:=1;i<len(needle);i++{
//前后缀不相等
for needle[i]!=needle[j]&&j>0{
j=next[j-1]
}
if needle[i]==needle[j]{
j++
}
next[i]=j
}
}