[AC自动机]个人对于AC自动机的理解

来源:互联网 发布:java精品课程网 编辑:程序博客网 时间:2024/06/05 01:45

首先,AC自动机应该是一个FA,即有限状态自动机。
任意一个有限状态自动机M都是一个五元组。
M= {Q,Σ,δ,q0,F}
Q是有限大小的状态集合,
Σ是字符集,
δ是转移函数,是一个状态集合到状态集合自身的对应关系。
q0是初始状态,
F是结束状态集合。

那么对于AC自动机而言,它是以trie树为基础,并以trie树结点为它的状态的自动机,它的五元组分别是如下含义:
Q是有限大小的状态集合,当自动机处于当前状态q时,表示trie树的根到状态q的路径上的字符与目前自动机已经接收的字符串的后缀相同,并且这个后缀尽可能长(但不与原串等长)。
Σ是trie树上的边上的字符的集合。
δ转移函数表示如下一个映射,当前状态为p,自动机接收了一个字符ch(chΣ),使自动机转移到另一个状态q。状态q满足,若p原本在trie树上就有一个儿子q,它们之间的连边为ch,那么转移函数就转移到q,如果不存在,则先找到一个状态failfail是这样一个状态:trie树的根到fail上的字符组成的串 是 trie树的根到p上的字符组成的串 的一个后缀,并且这个后缀尽可能长,如果fail在trie树上有一条ch边指向儿子q,转移函数就转移到q,否则我们继续找fail的fail,直到fail有一条ch边位置。
q0初始状态为空,即trie树的根。
F是结束状态集合,也即trie树上表示某一个串在此结束的那些点。

显然,由这些定义,我们就可以完成多模板串匹配,先将所有模板建成trie树,再建立上述AC自动机,对于每一个输入的串st,我们从trie树的根开始,然后不断按照转移函数δ走,每当我们走到一个结束状态时,就意味着我们找到了一个模板串。

那么如何构建AC自动机呢?
首先我们需要构建trie树,这样我们就构建好了除了转移函数以外的所有部分,那么剩下的我们就是要构建转移函数。
根据转移函数的定义,我们可以发现,假如当前状态是p,我们一直沿着p的fail一直走(显然failch无关,这是由fail的定义可以知道的),可以得到一条fail链。那么对于我当前一个字符ch,假如我一直沿着fail链往前条到某一个状态np才有trie边ch指向nq,那么pfp之间的所有点的转移函数对于字符ch都应转移到nq,因此我们可以递归地来计算转移函数,即:
对于目前状态p的转移函数δ(p,ch)
如果p存在trie边ch指向q,转移函数返回q
否则,如果p是初始状态,则返回p,否则返回delta(fail,ch)
那么又有一个问题,fail如何计算。
显然,由fail的定义我们知道,对于当前一个状态p,假如它有一个trie边ch指向q,那么q>fail=p>fail>delta(p>fail,ch)
特别地,初始状态的fail还是初始状态,并且一切接受了一个字符的状态(即trie树中根的儿子)的fail都为根。
这样我们就完成了AC自动机的构建。

代码如下:

inline void build(){ int l, r;    rt->fail = rt;    for(q[l = r = 0] = (int)rt; l <= r; l++){        Node *p = (Node *)q[l];        REP(i, 26)            if (p->nxt[i]) q[++r] = (int)(p->nxt[i]), p->nxt[i]->fail = p == rt ? rt : p->fail->nxt[i];            else p->nxt[i] = p == rt ? rt : p->fail->nxt[i];    }}

之后,我们发现AC自动机提供了一些副产品,比如fail树。
所谓fail树,就是把trie树中所有节点之间原来的边删去,仅保留fail边,然后反向所有fail边构成的树。
显然对于某个结点u,它的所有祖先都是它的后缀。
这样利用fail树我们可以知道对于给出的某一本字典,其中的任意一个单词在字典中出现了几次。

0 0
原创粉丝点击