AC 自动机
真的比 KMP 简单!(指好理解)
前置知识
trie 树(必会)
KMP(知道一点思想就行,不用详细学习)
用途
我们知道 KMP 能够
核心思想和 KMP 相似,除去多余的匹配,即在当前匹配状态的基础上转移着匹配。(我也不知道我说了啥)
基本结构
多个模式串,先去重前缀,建立前缀树(即 trie 树)。 假设我们当前有四个模式串:
对每个模式串终止字符记录其出现的次数作为其在 trie 树上对应节点的权值,然后遍历文本串的每一个字符,在 trie 树上向下走,
合并了一些模式串的前缀,于是有了一定优化,但是优化还不够,考虑对后缀优化。
fail 边
含义
AC 自动机的核心就是 fail 边。
fail 边的含义:如果
先不管怎样建 fail 边,按照要求人脑建立一下,如下图:
然后假设有文本串
为什么上一次匹配的时候,我们需要把文本串的指针回跳?因为没有办法除去后缀的影响——一个模式串是另一个模式串的后缀,或前者的一部分是后者的后缀。意味着:后者匹配失败不代表前者也会匹配失败。于是我们可以记录一下合法后缀,如果匹配失败就跳到自己最长后缀所在节点上(没有的话就跳根节点),容易发现这样一直跳下去,能把最开始的串的合法后缀全部跳完!说明后缀的影响已经排除掉了。
为了方便,我们把没有合法后缀的节点的 fail 边指向根节点。
实际上此时的 fail 边的含义:无法向下一个子节点匹配(没有子节点或匹配失败)时要走的边。
动画演示:
建立
BFS 的过程。
第一层(根节点为第零层)的 fail 全部指向根节点2,压入队列。
取出队头,遍历子节点,如果子节点存在,假设这个子节点代表字符
,当前节点 fail 指针指向 , 的子节点集合为 ,则将其 fail 指针指向 。
没了。
优化
我们一般把 trie 树的一个节点的儿子用长度为 26 的数组存下,导致遍历子节点的时候会遍历到傻逼空节点。
对于当前节点的空子节点指针,可以直接指向当前 fail 指针指向的节点的子节点集合中,对应的子节点。
参考代码:
1 | void build() { 复制 |