构建步骤

AC自动机的构造分成两步:

  1. 构造Trie树
  2. 在Trie树的基础上构建fail指针(相当于KMP的next数组)

    fail

    构造失败指针的过程概括起来就一句话:设这个节点上的字母为C,沿着它父亲节点的失败指针走,直到走到一个节点,它的子结点中也有字母为C的节点。然后把当前节点的失败指针指向那个字母也为C的儿子。如果一直走到了root都没找到,那就把失败指针指向root。
    具体操作起来只需要:先把root加入队列(root的失败指针指向自己或者NULL),这以后我们每处理一个点,就把它的所有儿子加入队列。

观察构造失败指针的流程:对照图来看。
(1) 首先root的fail指针指向NULL,然后root入队,进入循环。
(2) 从队列中弹出root,root节点与h、s节点相连,因为它们是第一层的字符,肯定没有比它层数更小的共同前后缀,所以把这2个节点的失败指针指向root,并且先后进入队列,失败指针的指向对应图中的(1),(2)两条红线;
(3) 从队列中先弹出h(左边那个),h所连的只有e结点,所以接下来扫描指针指向e节点的父节点h节点的fail指针指向的节点,也就是root,root->next[‘e’] == NULL,并且root->fail == NULL,说明匹配序列为空,则把节点e的fail指针指向root,对应图中的蓝色,然后节点e进入队列;
(4)从队列中弹出s,s节点与a,h(左边那个)相连,先遍历到a节点,扫描指针指向a节点的父节点s节点的fail指针指向的节点,也就是root,root->next[‘a’] == NULL,并且root->fail == NULL,说明匹配序列为空,则把节点a的fail指针指向root,对应图中的蓝色,然后节点a进入队列。
(5)接着遍历到h节点,扫描指针指向h节点的父节点s节点的fail指针指向的节点,也就是root,root->next[‘h’] != NULL,所以把节点h的fail指针指向右边那个h,对应图中的蓝色,然后节点h进入队列…由此类推,最终失配指针如图所示。

image.png

演示

Go创建AC

  1. package goAcAutoMachine
  2. type AcNode struct {
  3. fail *AcNode
  4. isPattern bool
  5. next map[rune]*AcNode
  6. }
  7. func newAcNode() *AcNode {
  8. return &AcNode{
  9. fail: nil,
  10. isPattern: false,
  11. next: map[rune]*AcNode{},
  12. }
  13. }
  14. type AcAutoMachine struct {
  15. root *AcNode
  16. }
  17. func NewAcAutoMachine() *AcAutoMachine {
  18. return &AcAutoMachine{
  19. root: newAcNode(),
  20. }
  21. }
  22. func (ac *AcAutoMachine) AddPattern(pattern string) {
  23. chars := []rune(pattern)
  24. iter := ac.root
  25. for _, c := range chars {
  26. if _, ok := iter.next[c]; !ok {
  27. iter.next[c] = newAcNode()
  28. }
  29. iter = iter.next[c]
  30. }
  31. iter.isPattern = true
  32. }
  33. func (ac *AcAutoMachine) Build() {
  34. queue := []*AcNode{}
  35. queue = append(queue, ac.root)
  36. for len(queue) != 0 {
  37. parent := queue[0]
  38. queue = queue[1:]
  39. for char, child := range parent.next {
  40. if parent == ac.root {
  41. child.fail = ac.root
  42. } else {
  43. failAcNode := parent.fail
  44. for failAcNode != nil {
  45. if _, ok := failAcNode.next[char]; ok {
  46. child.fail = parent.fail.next[char]
  47. break
  48. }
  49. failAcNode = failAcNode.fail
  50. }
  51. if failAcNode == nil {
  52. child.fail = ac.root
  53. }
  54. }
  55. queue = append(queue, child)
  56. }
  57. }
  58. }
  59. func (ac *AcAutoMachine) Query(content string) (results []string) {
  60. chars := []rune(content)
  61. iter := ac.root
  62. var start, end int
  63. for i, c := range chars {
  64. _, ok := iter.next[c]
  65. for !ok && iter != ac.root {
  66. iter = iter.fail
  67. }
  68. if _, ok = iter.next[c]; ok {
  69. if iter == ac.root { // this is the first match, record the start position
  70. start = i
  71. }
  72. iter = iter.next[c]
  73. if iter.isPattern {
  74. end = i // this is the end match, record one result
  75. results = append(results, string([]rune(content)[start:end+1]))
  76. }
  77. }
  78. }
  79. return
  80. }

参考

http://keyblog.cn/article-39.html