Aho-Corasick自动机算法(AC算法解读)

了解此算法,要有有线状态自动机基础。


该算法的基本思想是这样的:
在预处理阶段,AC自动机算法建立了三个函数,转向函数goto,失效函数failure和输出函数output,由此构造了一个树型有限自动机。
在搜索查找阶段,则通过这三个函数的交叉使用扫描文本,定位出关键字在文本中的所有出现位置。


此算法有两个特点,一个是扫描文本时完全不需要回溯,另一个是时间复杂度为O(n),时间复杂度与关键字的数目和长度无关。



对应模式集{he, she, his, hers}的树型有限自动机

goto函数:


fail函数:


output函数:

i           output(i)

2           {he}

5           {she,he}

7           {his}

9           {hers}


3. 转向,失效和输出函数的构建 
    现在说明如何根据一个关键字集建立正确的转向、失效和输出函数。整个构建包含两个部分,在第一部分确定状态和转向函数,在第二部分我们计算失效函数。输出函数的计算则是穿插在第一部分和第二部分中完成。
  为了构建转向函数,我们需要建立一个状态转移图。开始,这个图只包含一个代表状态0。然后,通过添加一条从起始状态出发的路径的方式,依次向图中输入每个关键字p。新的顶点和边被加入到图表中,以致于产生了一条能拼写出关键字p的路径。关键字p会被添加到这条路径的终止状态的输出函数中。当然只有必要时才会在图表中增加新的边。


首先说匹配算法:

算法1. Pattern matching machine
输入:text和M。text是x=a1a2...an,M是模式匹配自动机(包含了goto函数g(),failure函数f(),output函数output())
输出:text中出现的pat以及其位置。
 
state←0
for i←1 until n do //吞入text的ai
     while g(state, ai)=fail 
         state←f(state) //直到能走下去,呵呵,至少0那个状态怎么着都能走下去
     state←g(state,ai) //得到下一个状态
     if output(state)≠empty //能输出就输出
         print i;
         print output(state)



建立转向函数goto function:

</pre><pre name="code" class="cpp">算法2. Construction of the goto function
输入:patterns集合K={y1,y2,...,yk}
输出:goto function g 和output function output的中间结果
 
/*
We assume output(s) is empty when state s is first created, and g(s,a)=fail if a is
undefined or if g(s,a) has not yet been defined. The procedure enter(y) inserts into
the goto graph a path that spells out y.
*/
 
newstate←0
fori ← 1until k //对每个模式走一下enter(yi),要插到自动机里来嘛
     enter(yi)
for all a such that g(0,a)=fail
     g(0,a)←0
 
 
enter(a1a2...am)
{
     state←0;j←;1
     while g(state,aj)≠fail //能走下去,就尽量延用以前的老路子,走不下去,就走下面的for()拓展新路子
         state←g(state,aj)
         j←j+1
 
     for p←j until m //拓展新路子
         newstate←newstate+1
         g(state,ap)←newstate
         state←newstate
 
     output(state)←{a1a2...am} //此处state为每次构造完一个pat时遇到的那个状态
}

Failure function的构造:(这个比较抽象)

大家注意状态0不在failure function中,下面开始构造,首先对于所有depth为1的状态s,f(s)=0,然后归纳为所有depth为d的状态的failure值都由depth-1的状态得到。

具体讲,在计算depth为d的所有状态时候,我们会考虑到每一个depth为d-1的状态r

1.       如果对于所有的字符a,g(r,a)=fail,那么什么也不做,我认为这时候r已经是trie树的叶子结点了。

2.       否则的话,如果有g(r,a)=s,那么执行下面三步

(a)       设置state=f(r) //state记录跟r共前缀的东东

(b)       执行state=f(state)零次或若干次,直到使得g(state,a)!=fail(这个状态一定会有的因为g(0,a)!=fail)//必须找条活路,能走下去的

(c)       设置f(s)=g(state,a),即相当于找到f(s)也是由一个状态匹配a字符转到的状态。

实例分析:

首先我们将depth为1的状态f(1)=f(3)=0,然后考虑depth为2的结点2,6,4

计算f(2)时候,我们设置state=f(1)=0,因为g(0,e)=0,所以f(2)=0;

计算f(6)时候,我们设置state=f(1)=0,因为g(0,i)=0,所以f(6)=0;

计算f(4)时候,我们设置state=f(3)=0,因为g(0,h)=1,所以f(4)=1;

然后考虑depth为3的结点8,7,5

计算f(8)时候,我们设置state=f(2)=0,因为g(0,r)=0,所以f(8)=0;

计算f(7)时候,我们设置state=f(6)=0,因为g(0,s)=3,所以f(7)=3;

计算f(5)时候,我们设置state=f(4)=1,因为g(1,e)=2,所以f(5)=2;

最后考虑depth为4的结点9

计算f(9)时候,我们设置state=f(8)=0,因为g(0,s)=3,所以f(9)=3;

 

具体算法:


算法3. Construction of the failure function
输入:goto function g and output function output from 算法2
输出:failure function f and output function output
 
queue←empty  //首先清空队列
for each a such that g(0,a)=s 且 s≠0  //把状态0的所有后续非0状态存入临时队列
     list_add_tail(queue, s)  
     f(s)←0
 
while queue≠empty {
     pop(queue);  //每次循环弹出queue中的首个state r 【r的深度为当前深度】
     for each a such that g(r,a)=s 且 s≠fail //r是队列头状态,如果r遇到a能走下去,【s的深度是r的深度+1】
<pre name="code" class="cpp">         list_add_tail(queue, s) <span style="font-family: Arial, Helvetica, sans-serif;"> //把这个新的状态放到队列</span>

<span style="white-space:pre">	</span> //start:找到r的失败状态state的条件为字符a的状态(还是保存在state变量中)
state←f(r) while g(state,a)=fail state←f(state)
 
<pre name="code" class="cpp"><span>	</span> //end:找到r的失败状态state的条件为字符a的状态(还是保存在state变量中)
f(s)←g(state,a) //把此时的状态state作为s状态的匹配失败后的后续状态(注意,s的深度是r的深度+1) }
 



  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值