[Hdu 2222] 字符串(三) {Aho-Corasick自动机}

{

继续介绍字符串的相关内容

这篇文章介绍Aho-Corasick自动机

}

 

Aho-Corasick自动机

用于解决 多模式串匹配 的问题

首先得了解 KMP算法Trie树的相关理论

 

先看一个具体的问题

Hdu 2222 http://acm.hdu.edu.cn/showproblem.php?pid=2222

题意 给定N个模式串 统计在一个长为M的主串里出现了多少个模式串

KMP算法可以做 复杂度为O(MN)

结合KMP算法和Trie树 AC自动机可以很好的解决这个问题

AC自动机的第一步是把所有模式串建成一个Trie

比如有模式串{SHE SHR SAY HE HR HER}

第一步.建立相应的Trie

Trie_Build
   
   
readln(n,m);
tt:
= 0 ;
allot(root);
for i: = 1 to n do
begin
p:
= root;
while not eoln do
begin
read(ch);
k:
= tr[ch];
if s[p][k] = 0
then allot(s[p][k]);
p:
= s[p][k];
end ;
d[p]:
= true;
readln;
end ;

 

 

[Hdu 2222] 字符串(三) {Aho-Corasick自动机}_第1张图片

主串为SHERSAY

和KMP算法的策略基本相同

AC自动机是从根节点开始根据主串的字母在Trie上走

运气很好 我们只要在Trie上走三步就配到一个串SHE

正当我们想要继续的时候 发现下一个字母是S 在当前节点X上不能再向下了

只好再回头跳到根节点继续配

[Hdu 2222] 字符串(三) {Aho-Corasick自动机}_第2张图片

显然这样不断地 匹配就向下 不匹配就回溯到根 是可以得到正解的

但是最坏复杂度还是O(MN)

我们回想KMP算法 既然要回溯 不如回的少一点

我们在SHE配好之后 抬头一看 有个单词的前缀是HE 正好是SHE的后缀

我们就跳到HE的词尾节点 继续配这样就减少了运算量

[Hdu 2222] 字符串(三) {Aho-Corasick自动机}_第3张图片

假设还有E这个单词怎么办 我们显然是不往上回溯的 因为后缀E没有后缀HE长嘛

我们发现到了新的节点可以配出R这个字母 就继续向下 走了一步又发现无路可走了

抬头一看 什么也没发现 无奈之下回到根节点继续配 又欣喜的发现SAY可以配了

如此就匹配完了 由于走到了四个模式串的词尾

所以这个主串出现了 SHE HE HER SAY四个串

回顾我们的探索历程 我们借鉴了KMP的思路 利用了回溯来降低运算量

从SHE词尾到HE词尾 从HER词尾到Root 有两次回溯

这就是AC自动机的核心思想 Fail指针 顾名思义就是匹配失败了 回溯到哪里继续

这个和KMP的Next函数十分类似

回忆KMP的Next函数我们是通过线性递推来构造的

Trie是一棵树 我们就通过BFS来实现吧

下图是构造好Fail指针的AC自动机

[Hdu 2222] 字符串(三) {Aho-Corasick自动机}_第4张图片

第二步.构建Fail指针

Fail_Build
   
   
h: = 1 ; t: = 0 ;
f[root]:
= root;
for i: = 1 to 26 do
if s[root][i] <> 0
then begin
inc(t);
q[t]:
= s[root][i];
f[q[t]]:
= root;
end ;
while h <= t do
begin
for i: = 1 to 26 do
if s[q[h]][i] <> 0
then begin
inc(t);
q[t]:
= s[q[h]][i];
p:
= f[q[h]];
while (p <> root) and (s[p][i] = 0 ) do
p:
= f[p];
if s[p][i] = 0
then f[q[t]]: = root
else f[q[t]]: = s[p][i];
if d[f[q[t]]]
then d[q[t]]: = true;
end ;
inc(h);
end ;

 

我们看到这段程序 第一步是把根节点的儿子都处理好并入队

 

由于根节点的特殊性 我们不好把它的儿子和其他节点统一处理 于是就一个循环解决

然后开始BFS 我们把队首的儿子都依次处理好然后入队即可

寻找儿子的Fail指针 和KMP类似 从父亲开始不断的利用长辈的Fail指针回溯

[Hdu 2222] 字符串(三) {Aho-Corasick自动机}_第5张图片

直到一个长辈节点有一个儿子 和 父亲的这个儿子一样为止

把儿子的Fail指针连向长辈的儿子

如果走到根节点都没有这样的长辈 就把Fail指向Root

队空就结束了

注意到虚线代表的串 前面的串是后面串的后缀

理解这个BFS就不难了

 

第三步.匹配

就是根据主串在自动机上走

匹配成功就向下走一步 不成功就沿着Fail指针回溯

复杂度达到了O(M)

很好理解 注意统计的细节即可

 

AC_Run
   
   
ans: = 0 ;
p:
= root;
while not eoln do
begin
read(ch);
c:
= ord(ch) - 96 ;
while (p <> root) and (s[p][c] = 0 ) do
p:
= f[p];
if s[p][c] = 0
then p: = root
else p: = s[p][c];
temp:
= p;
while temp <> root do
begin
if g[temp] <> 0
then begin
ans:
= ans + g[temp];
g[temp]:
= 0 ;
end ;
temp:
= f[temp];
end ;
end ;
writeln(ans);
readln;

 

(本文的代码都是从几个不同的问题中抠来的...

一时翻不到2222的代码了 不过还是可以读的懂的吧)

这样基本的多模式串匹配就介绍完了

下一篇文章将是介绍AC自动机的应用和衍生出的有限状态自动机

 

BOB HAN 原创 转载请注明出处 http://www.cnblogs.com/Booble/

你可能感兴趣的:(字符串)