您的位置:首页 > 其它

AC 经典多模式匹配算法

2009-10-28 09:41 399 查看
今天说说多模式匹配
AC

算法(
Aho and Corasick

),感谢追风侠帮忙整理资料,while(1) {Juliet.say("3Q");}。前面学习了
BM


Wu-Manber

算法,
WM


BM

派生,不过
AC

与它们无染,是另外一种匹配思路。

1.


初识
AC


算法


Step1:

将由
patterns

组成的集合(要同时匹配多个
patterns

嘛)构成一个有限状态自动机。

Step2:

将要匹配的
text

作为自动机输入,输出含有哪些
patterns


patterns

在全文中位置。

自动机的执行动作由三个部分组成:

(1)

一个
goto function

(2)

一个
failure function

(3)

一个
output function

我们先通过一个具体实例来了解一下这三个部分,及该自动机的运作方式。先有个大概印象,后面会具体讲解。
patterns

集合
{he, she, his ,hers}

,我们要在
”ushers”

中查找并匹配。



(1) goto function

i
1
2
3
4
5
6
7
8
9

f(i)
0
0
0
1
2
0
3
0
3

(发现没?貌似
i


f(i)

有相同前缀哦^_^)

(2) failure function



i
output(i)

2
{he}

5
{she,he}

7
{his}

9
{hers}

(3) output function



首先我们从状态
0

开始,接收匹配字符串的第一个字符
u

,在
goto

(简称
goto function

)中可以看到回到状态
0

,接着第二个字符
s

,发现转到状态
3

,在
output

中查找一下
output(3)

为空字符串,说明没有匹配到
patterns

。继续匹配
h

,转到状态
4

,查找
output

发现仍然没有匹配,继续字符
e

,状态转到了
5

,查找
output

,发现
output(5)

匹配了两个字符串
she


he

,并输出在整个字符串中的位置。然后接着匹配
r

,但发现
g(5,r)=fail

,这时候我们需要查找
failure

,发现
f(5)=2

,所以就转到状态
2

,并接着匹配
r

,状态转移到了
8

,接着匹配
s

,状态转移到了
9

,查看
output

,并输出
output(9):hers

,记录下匹配位置。至此字符串
ushers

匹配完毕。

具体的匹配算法如下:

算法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)

AC算法的时间复杂度是O(n),与patterns的个数及长度都没有关系。因为Text中的每个字符都必须输入自动机,所以最好最坏情况下都是O(n),加上预处理时间,那就是O(M+n),M是patterns长度总和。

2.


构造三表


OK

,下面我们看看如何通过
patterns

集合构造上面的
3


function

这三个
function

的构造分两个阶段:

(1)

我们决定状态和构造
goto function

(2)

我们计算得出
failure function


output function

的构造贯穿于这两个阶段中。

2.1 goto



ouput


填表



我们仍然拿实例来一步步构造:
patterns

集合
{he,she,his,hers}

首先我们构造
patterns he



然后接着构造
she



再构造
his

,由于在构造
his

的时候状态
0

接收
h

已经能到状态
1

,所以就不用重新建一个状态了,有点像建
trie

树的过程,共用一段相同的前缀部分



最后构造
hers



具体构造
goto function

算法如下:

算法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
时遇到的那个状态

}

2.2
Failure



output


填表


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
≠0//

其实这是广搜BFS
的过程

queue
←queue
∪{s}

f(s)
←0

while
queue
≠empty

pop();

for
each
a such that g(r,a)=s
≠fail //r

是队列头状态,如果r
遇到a
能走下去

queue
←queue
∪{s} //

那就走

state
←f(r) //

与r
同前缀的state

while
g(state,a)=fail //

其实一定能找着不fail
的,因为至少g(0,a)
不会fail

state
←f(state)

f(s)
←g(state,a) //OK

,这一步相当于找到了s
的同前缀状态,即f(s)

output(s)
←output(s)
∪output(f(s)) //

建议走一下例子中g(4,e)=5
的例子,然后ouput(5)
∪output(2)={she,he}

2.3
output



Output function

的构造参见算法
2


3

3.


算法优化


改进
1




观察一下算法
3

中的
failure function

还不够优化



我们可以看到
g(4,e)=5

,如果现在状态到了
4

并且当前的字符为
t


=e

,因为
g(4,t)=fail



所以就根据
f(4)=1,

跳转到状态
1

,而之前已经知道
t


=e

,所以就没必要跳到
1

,而直接跳到状态
f(1)=0



为了避免不必要的状态迁移,和
KMP

算法有异曲同工之处。重新定义了一个
failure function


f1

f1(1)=0



对于
i>1,

如果能使状态
f(i)

转移的所有字符也能使
i

状态转移,那么
f1(i)=f1(f(i))



否则
f1(i)=f(i)



改进
2




由于
goto function

中并不是每个状态对应任何一个字符都有状态迁移的,当迁移为
fail

的时候,我们就要查
failure function

,然后换个状态迁移。现在我们根据
goto function


failure function

来构造一个确定的有限自动机
next move function

,该自动机的每个状态遇到每个字符都可以进行状态迁移,这样就省略了
failure function



构造
next move function

的算法如下:

算法4
:Construction of a deterministic finite automaton



输入:goto functioni g and failure function f

输出:next move function delta

queue
←empty

for
each
symbol a

delta(0,a)
←g(0,a)

if
g(0,a)
≠0

queue
←queue
∪g(0,a)

while
queue
≠empty

pop()

for
each
symbol a

if
g(r,a)=s
≠fail

queue
←queue
∪{s}

delta(r,a)
←s

else
delta(r,a)
←delta(f(r),a)

Next function delta

的计算如下:



其中
’.’

表示除了该状态能识别字符的其他字符。

改进
2

有利有弊:好处是能减少状态转移的次数;坏处是由于状态与状态之间的迁移多了,导致存储的空间比较大。
http://blog.csdn.net/ijuliet/archive/2009/05/24/4210858.aspx
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: