Kmp ac自动机
学习一下ac自动机,多个模式串匹配一串文本,找到文本中模式串匹配的个数
package main
import (
"fmt"
)
//字典树(前缀树),ac自动机基础
type Node struct { //字典树
next [26]* Node
fail *Node //失配指针
flag bool
}
//构建字典树
func build(root *Node ,str string){
pre:=root
var ptr *Node
order:=0 //索引
for i:=0;i<len(str);i++{
order = int(str[i]-'a')
if pre.next[order]==nil{
ptr = new(Node)
for j:=0;j<26;j++{
ptr.next[j] = nil
}
ptr.flag = false
ptr.fail = nil
pre.next[order] = ptr //设置下标
}
pre = pre.next[order]
}
pre.flag = true
}
//寻找失配指针,从根开始,把子节点的失配找到
func disposeFail(root *Node){
//层序遍历
queue:=make([]*Node,0)
var pre,ptr *Node
queue = append(queue,root)
for len(queue)!=0{
pre=queue[0]
queue = queue[1:]
ptr=nil //每次都初始化
for i:=0;i<26;i++{
if pre.next[i]!=nil{ //子节点存在
if pre==root{ //根则
pre.next[i].fail = root
}else{
ptr = pre.fail
for ptr!=nil{ //向上找失配对的指针
if ptr.next[i]!=nil{
pre.next[i].fail = ptr
break
}
ptr = ptr.fail
}
if ptr==nil{ //回溯到root,则失配指向root
pre.next[i].fail = root
}
}
queue = append(queue,pre.next[i]) //子节点加入队列
}
}
}
}
//具体匹配
func matchMultiPattern(root *Node,str string)int{
order:=0
count:=0
var pre,ptr *Node
pre = root
for i:=0;i<len(str);i++{
order = int(str[i]-'a')
for pre.next[order]==nil&&pre!=root{
pre = pre.fail
}
//找模式串
pre = pre.next[order]
if pre==nil{ //没找到开启下一轮
pre = root
continue
}
ptr = pre //匹配该点之后沿失败回溯,判断其他点
for ptr!=root{
if ptr.flag==true{ //到模式串结尾,需要1决定是否增加1
count++
ptr.flag=false //计算过,改为false
}else{
break
}
ptr = ptr.fail //找失配指针1
}
}
return count
}
func main(){
trim:=new(Node)
strs:=[]string{"long","java","life","python","short"}
for _,v:=range strs{
build(trim,v)
}
//构建失配
disposeFail(trim)
fmt.Println(matchMultiPattern(trim,"lifeisshortsoistudypython"))
}
kmp匹配
- 经典写法,先求next数组再计算
next数组为i前面字符前缀后缀最长公共子串的长度,求next较难理解
next[k]表示p[k]前面有相同前缀尾缀的长度(这个长度是按最大算的),
p[next[k]]表示相同前缀的最后一个字符,如果p[next[k]]==p[k],
则可以肯定next[k+1]=next[k]+1,如果p[next[k]]!=p[k],则思考,
既然next[k]长度的前缀尾缀都不能匹配了,
是不是应该缩短这个匹配的长度,到底缩短多少呢,
next[next[k]]表示p[next[k]]前面有相同前缀尾缀的长度(这个长度是按最大算的),
所以一直递推就可以了,因为这个缩小后的长度都是按最大的算的
- 代码
// 求next数组,next数组表示 到i为止,前缀和后缀中最长公共字串长度
func GetNext(str string)[]int{
next:=make([]int,len(str))
next[0] = -1
j:=0
k:=-1
for j<len(str)-1{ // 后续j++,防止越界
if k==-1||str[j]==str[k]{
j++
k++
next[j] = k
}else{
k = next[k]
}
}
return next
}
//单个匹配
func Kmp(str string,ptr string)int{ //返回相匹配到位置下标,只返回第一个
i,j:=0,0 //主串和模式串
next:=GetNext(ptr) //模式串next数组
for i<len(str)&&j<len(ptr){
if j==-1||str[i]==ptr[j]{ //不要忘记j==-1的情况
i++
j++
}else{
//i 不变,暴力解法时候 i = i-j+1
j = next[j]
}
}
if j==len(ptr){
return i-j //初始位置
}else{
return -1 //不匹配
}
}
func main() {
str := "ABABABC"
p:="ABA"
one:="hello"
two:="ll"
fmt.Println(Kmp(str,p)) //单个
fmt.Println(Kmp(one,two))
}