package ACautomaton
import (
"testing"
)
/*
Ac 自动机解决的问题
在一个大文章当中,给你一批候选串,想知道文章当中命中了哪些候选串,返回List 其中包含命中的字符串
如何高效一点
如何理解fail指针
候选串建立一些前缀树,每个候选串都建立前缀树
“abcd”
"bcd"
"ce"
建立前缀树
0
a / b| \c
0 0 0
b / c| \ e
0 0 0
c / d /
0 0
d /
0
每一个节点多出一个fail指针
头节点的fail指针指向空
第一级孩子的fail指针指向头
再接下来的fail指针策略
1.假设来到X节点,我要设置我的某一个孩子节点的fail指针, 它指向哪
首先看,我是怎么来到我的孩子的,假设路径'b'
X -- > 甲 有走向b的路径
|b \
子-----> 甲子
假设X的fail指针指向甲, 如果甲节点也有走向b的路,我就把我孩子的fail指针指向它的孩子
1.我自己的fail指针指向空,让我孩子的fail指针直接指向我
2.当前节点的fail指针指向空,让我孩子的指针指向头
X---fail---> nil
\
X子------fail----->head
head------>nil
/ \ `----.
| | \
0 | 0 / 0 |
a | | b | / | /
1 0' .----->0'2 .-----> 0' 3
b | / c | / e |
0' 0' 0
c | d |
0 0
d |
0
0
a / b| \c
0 0 0
b / c| \ e
0 0 0
c / d /
0 0
d /
0
总逻辑,一条
首先来到父亲节点,设置孩子节点M的fail指针,假设路径叫a, 从父节点先跳一次,找到X节点,若X节点也有指向a的路,则把节点M的fail指向X的孩子节点
如果X节点没有指向a的路,则从X往外跳,重复以上步骤,若某个节点的fail指针指向空,仍没有找到fail的指向,则将M节点的fail指针指向头节点
遇到空的前一个一定是头
加fail指针的时机是建立好前缀树之后,再按照宽度优先遍历,建立fail指针
前缀树是压缩的,a开头的只能有一个
*/
type Node struct {
end int // 有多少个字符串以该节点结尾
fail *Node
nexts []*Node
}
func NewNode() *Node {
return &Node{
end: 0,
fail: nil,
nexts: make([]*Node,26),// 26个字母
}
}
type ACAutomation struct {
root *Node
}
func NewACAutomation() *ACAutomation {
return &ACAutomation{
root: NewNode(),
}
}
// 你有多少个匹配串,就调用多少次insert
func (ac *ACAutomation) insert(str string) {
cur := ac.root
index := 0
for i := 0; i < len(str); i++ {
index = int(str[i] - 'a')
if cur.nexts[index] == nil {
cur.nexts[index] = NewNode()
}
cur = cur.nexts[index]
}
cur.end++
}
type Queue struct {
elem []interface{}
length int
}
func NewQueue() *Queue {
return &Queue{
elem: make([]interface{},0),
}
}
func (q *Queue) IsEmpty() bool {
return q.length == 0
}
func (q *Queue) Add(value interface{}) {
q.elem = append(q.elem,value)
q.length++
}
func (q *Queue) Poll() interface{} {
res := q.elem[0]
q.elem = q.elem[1:]
q.length--
return res
}
func (ac *ACAutomation) build() {
queue := NewQueue()
queue.Add(ac.root)
var cur, cfail *Node
for !queue.IsEmpty() {
cur = queue.Poll().(*Node) // 父
for i := 0; i < 26; i++ { // 下级所有的路
if cur.nexts[i] != nil { // 该路下有子节点
cur.nexts[i].fail = ac.root // 初始时先设置一个值
cfail = cur.fail
for cfail != nil { // cur不是头节点
if cfail.nexts[i] != nil {
cur.nexts[i].fail = cfail.nexts[i]
break
}
cfail = cfail.fail
}
queue.Add(cur.nexts[i])
}
}
}
}
func (ac *ACAutomation)containNum(str string) int {
cur := ac.root
var follow *Node
index := 0
ans := 0
for i := 0; i < len(str); i++ {
index = int(str[i] - 'a')
for cur.nexts[index] == nil && cur != ac.root {
cur = cur.fail
}
if cur.nexts[index] != nil {
cur = cur.nexts[index]
}else {
cur = ac.root
}
follow = cur
for follow != ac.root {
if follow.end == -1 {
break
}
{ // 不同的需求,在这一段{ }之间修改
ans += follow.end
follow.end = -1
} // 不同的需求,在这一段{ }之间修改
follow = follow.fail
}
}
return ans
}
func TestAc(t *testing.T) {
ac := NewACAutomation()
ac.insert("dhe")
ac.insert("he")
ac.insert("c")
ac.insert("w")
ac.build()
t.Log(ac.containNum("wchedhe"))
}
注:本人不是算法的原作者,我只是用Go重写了Java的代码,参照该地址,版权归原作者所有!
package Ac2
import "testing"
// 前缀树的节点
type Node struct {
// 如果一个node,end为空,不是结尾
// 如果end不为空,表示这个点是某个字符串的结尾,end的值就是这个字符串
end string
// 只有在上面的end变量不为空的时候,endUse才有意义
// 表示,这个字符串之前有没有加入过答案
endUse bool
fail *Node
nexts []*Node
}
func NewNode() *Node {
return &Node{
end: "",
endUse: false,
fail: nil,
nexts: make([]*Node,26),
}
}
type ACAutomation struct {
root *Node
}
func NewACAutomation() *ACAutomation {
return &ACAutomation{
root: NewNode(),
}
}
func (ac *ACAutomation) insert(str string) {
cur := ac.root
index := 0
for i := 0; i < len(str); i++ {
index = int(str[i] - 'a')
if cur.nexts[index] == nil {
cur.nexts[index] = NewNode()
}
cur = cur.nexts[index];
}
cur.end = str
}
func (ac *ACAutomation) build() {
queue := NewQueue()
queue.Add(ac.root)
var cur, cfail *Node
for !queue.IsEmpty() {
// 某个父亲,cur
cur = queue.Poll().(*Node)
for i := 0; i < 26; i++ { // 所有的路
// cur -> 父亲 i号儿子,必须把i号儿子的fail指针设置好!
if cur.nexts[i] != nil { // 如果真的有i号儿子
cur.nexts[i].fail = ac.root
cfail = cur.fail
for cfail != nil {
if cfail.nexts[i] != nil {
cur.nexts[i].fail = cfail.nexts[i]
break
}
cfail = cfail.fail
}
queue.Add(cur.nexts[i])
}
}
}
}
func (ac *ACAutomation) containWords(str string) []string {
cur := ac.root
var follow *Node
index := 0
ans := make([]string,0)
for i := 0; i < len(str); i++ {
index = int(str[i] - 'a') // 路
// 如果当前字符在这条路上没配出来,就随着fail方向走向下条路径
for cur.nexts[index] == nil && cur != ac.root {
cur = cur.fail
}
// 1) 现在来到的路径,是可以继续匹配的
// 2) 现在来到的节点,就是前缀树的根节点
if cur.nexts[index] != nil {
cur = cur.nexts[index]
}else {
cur = ac.root
}
follow = cur
for follow != ac.root {
if follow.endUse {
break
}
// 不同的需求,在这一段之间修改
if follow.end != "" {
ans= append(ans,follow.end)
follow.endUse = true
}
// 不同的需求,在这一段之间修改
follow = follow.fail
}
}
return ans
}
func TestAc(t *testing.T) {
ac := NewACAutomation()
ac.insert("dhe")
ac.insert("he")
ac.insert("abcdheks")
// 设置fail指针
ac.build()
contains := ac.containWords("abcdhekskdjfafhasldkflskdjhwqaeruv");
for _, word := range contains {
t.Log(word)
}
}
type Queue struct {
elem []interface{}
length int
}
func NewQueue() *Queue {
return &Queue{
elem: make([]interface{},0),
}
}
func (q *Queue) IsEmpty() bool {
return q.length == 0
}
func (q *Queue) Add(value interface{}) {
q.elem = append(q.elem,value)
q.length++
}
func (q *Queue) Poll() interface{} {
res := q.elem[0]
q.elem = q.elem[1:]
q.length--
return res
}
func TestQueue(t *testing.T) {
q := NewQueue()
q.Add(1)
q.Add(2)
q.Add(3)
for !q.IsEmpty() {
t.Log(q.Poll())
}
}