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))
 }

posted @ 2021-09-11 11:44  海拉尔  阅读(31)  评论(0编辑  收藏  举报