zoukankan      html  css  js  c++  java
  • AhoCorasick自动机

    [Hdu 2222] 字符串(三) {Aho-Corasick自动机} - Master_Chivu - 博客园
    http://www.cnblogs.com/Booble/archive/2010/12/05/1897121.html
    Var Bob:^Joy;
    While Working, We're Worthy. Simple,Efficient,and Beautiful.
    [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;





    主串为SHERSAY

    和KMP算法的策略基本相同

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    第二步.构建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指针回溯

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

    把儿子的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自动机的应用和衍生出的有限状态自动机



  • 相关阅读:
    NSString
    xib和storyboard的使用方法
    drawRect画线和使用CGContext
    CGAffineTransform动画
    【概念】静态成员
    【c#】Application.Exit和Close有什么不同
    【c#】const和readonly关键字
    【概念】设计模式
    【概念】常见数据结构与算法
    【概念】索引器
  • 原文地址:https://www.cnblogs.com/lexus/p/2199932.html
Copyright © 2011-2022 走看看