计算机

AC 自动机

AC 自动机(Aho–Corasick automaton)是一种将 Trie 和 KMP 算法两者结合的自动机,用于多个模式串的同时匹配。

概述

概念

AC 自动机,在 Trie 的基础上连接新的边得到。其节点(状态)与 Trie 相同,但是不同于 Trie 的不匹配就直接推出,AC 自动机的每个节点接受一个字符后,都会转移到新的节点上。

如何判断一个节点匹配失败后应该转移到哪里呢?我们可以记录:这个节点所对应字符串,在 Trie 中的能找到的最长真后缀对应的节点,并用指针 fail 表示。这个概念非常类似于 (链接未公开) 的 border,实际上,KMP 的状态可以看做退化成链的 Trie,此时最长真后缀就等同于 border。

例如,字符串 abab、abc、bca、cc 形成的 Trie 如下图,则其中蓝色箭头表示 fail 边。

AC 自动机还可以进一步进行路径压缩。实际上,当 fail 边确定后,读入一个字符 c 后仍然可能沿 fail 边跳转多次才找到下一个匹配点(像 KMP 算法那样)。对每个节点 i,我们可以直接记录所有可能字符 c 对应的下一个节点,记录为数组 next,这里用 \operatorname{next}(i, c) 表示。

建立

当 p 不为根节点时,有

\operatorname{fail}(\operatorname{next}(p, c)) = \operatorname{next}(\operatorname{fail}(p), c).

特殊地,对根节点 r 有 \operatorname{fail}(\operatorname{next}(r, c)) = r。而 fail 边一定指向深度更小的节点,依据此公式,在 Trie 上层次遍历一次,就可以同时处理出 AC 自动机的 next 和 fail 指针。

建树的代码如下:

void ac_build()
{
    rt.fail = &rt;
    queue<Node*> qu;
    qu.push(&rt);
    while (!qu.empty())
    {
        Node *u = qu.front();
        qu.pop();
        for (size_t i = 0; i < CHAR_SIZE; i++)
        {
            Node *&v = u->next[i];
            if (v == nullptr)
            {
                if (u == &rt)
                {
                    v = u;
                }
                v = u->fail->next[i];
                continue;
            }
            if (u == &rt)
            {
                v->fail = u;
            }
            else
            {
                v->fail = u->fail->next[i];
            }
            qu.push(v);
        }
    }
}
BEYOND THE ARTICLE

读完了,有什么想法?

阅读已保存的问答示例,并循着引用回到原文。

查看公开问答示例
— 本文完 — 返回文章列表