AC自动机——文章中是否出现多个字符串

在这里插入图片描述

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

注:本人不是算法的原作者,我只是用Go重写了Java的代码,参照该地址,版权归原作者所有!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

metabit

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值