zoukankan      html  css  js  c++  java
  • AC自动机讲解

      在没学AC自动机之前以为这是一个很高深很难的算法,但其实AC自动机并不难,理解之后就变得非常简单了。

      先来介绍一下AC自动机:AC自动机全称Aho-Corasick automaton(不是Accept自动机qwq),是著名的多模匹配算法,在多模匹配问题上相比于kmp效率更快。举个例子:询问多个单词在一篇文章中是否出现过,kmp要把每个单词都和文章匹配一次,但AC自动机只要匹配一次就能知道哪些单词出现过。

      想学AC自动机的基础是先学会trie树(就是字典树)和kmp(个人觉得这个没啥必要,了解一下就好)。AC自动机上一个最重要的东西就是失配指针(fail),有了这个东西才能进行匹配。trie树上每个点都有且只有一个失配指针,失配指针指向的是以当前点所表示的字符为最后一个字符的字符串(也就是根到当前点组成的字符串)的最长后缀的最后一个节点。举个例子:有两个串,分别为abc和bc,每个字符所在点的标号依次为1(a)2(b)3(c),4(b)5(c)。3的失配指针指向的点就是5,2指向的点就是4,如果根到这个点组成的字符串没有后缀(例如1),那它的失配指针就指向根节点(根节点失配指针是自己)。

      那么AC自动机是怎么匹配的呢?我们用一个图来解释(用图比较形象qwq)。

                                           

      这里有四个串:abcd,abd,bce,cd。图中红色箭头就表示这个点的失配指针指向的点现在给出一个原文本abcecd,询问这四个单词有哪个在原文本中出现过。从根节点开始走发现子节点中有'a'字符(1节点),然后走到1节点,再往下走,一直走到3节点,发现3的子节点没有'e',那就跳到3的失配指针7节点再往下发现有'e'了再走下去。到了8节点,下面没有节点了,就跳到它的失配指针(根节点)然后继续往下走,直到匹配结束了为止(如果匹配到一个字符,匹配不下去了且当前在根节点,就跳过这个字符匹配下一个)。原文本串在AC自动机上经过了两个终止节点(8和10),因此这两个串在原文本中出现过。

    下面是代码时间

    建trie树

    void build(char *s)
    {
        int l=strlen(s);
        int now=0;
        for(int i=0;i<l;i++)
        {
            int x=(s[i]-'a');
            if(a[now].vis[x]==0)
            {
                a[now].vis[x]=++cnt;
            }
            now=a[now].vis[x];
        }
        a[now].end++;
    } 

    找失配指针

    void bfs()
    {
        queue<int>q;
        for(int i=0;i<26;i++)
        {
            if(a[0].vis[i]!=0)
            {
                a[a[0].vis[i]].fail=0;
                q.push(a[0].vis[i]);
            }
        }
        while(!q.empty())
        {
            int now=q.front();
            q.pop();
            for(int i=0;i<26;i++)
            {
                if(a[now].vis[i]!=0)
                {
                    a[a[now].vis[i]].fail=a[a[now].fail].vis[i];
                    q.push(a[now].vis[i]);
                }
                else
                {
                    a[now].vis[i]=a[a[now].fail].vis[i];
                }
            }
        }
    }

    AC自动机匹配

    int Aho_Corasick_automaton(char *s)
    {
        int l=strlen(s);
        int now=0;
        int ans=0;
        for(int i=0;i<l;i++)
        {
            int x=(s[i]-'a');
            now=a[now].vis[x];
            for(int t=now;t&&a[t].end!=-1;t=a[t].fail)
            {
                ans+=a[t].end;
                a[t].end=-1;
            }
        }
        return ans;
    }
    

      变量名解释:fail,失配指针;end,终止节点;vis,每个点的子节点。

      从AC自动机匹配的函数中可以发现并没有失配指针什么事情qwq,其实在找失配指针时就已经把一个节点失配指针对应的子节点连在了这个点的子树上(严格来讲这样做就已经把AC自动机转化成trie图了,真正的AC自动机是跳fail指针的,不过这样写时间复杂度会更优,因此也可以当做AC自动机来写),还是以上面那个图为例,因为3节点没有'e'这个节点,所以就把7节点的子节点中的'e'(8节点)接在3节点下方,这样在AC自动机匹配时直接像遍历图一样往下遍历就行了。

      最后再说一下和AC自动机有关的一个东西——fail树。AC自动机上每个节点都有一个失配指针,那么将每个点的失配指针连向这个点,就形成了一棵树,一般称为fail树。在fail树上一个节点的所有祖先节点在AC自动机上所代表的串都是这个节点在AC自动机上所代表的串的后缀,因为如果一个串X的末节点的失配指针直接或间接地指向Y串的末节点,那么Y串的末节点在fail树上一定是X串末节点的祖先。有许多AC自动机的题都能利用fail树的性质来解题。

    推荐练习题(难度递增):

    BZOJ2938病毒

    BZOJ3172单词

    BZOJ4502串

    BZOJ1212L语言

    BZOJ1030文本生成器

    BZOJ3530数数

    BZOJ3075Necklace

    BZOJ1195最短母串

    BZOJ3881Divljak

    BZOJ2434阿狸的打字机

    (其中BZOJ1212可以用AC自动机,但我用了trie树)

  • 相关阅读:
    java中断
    guava cache使用和源码分析
    redis基本类型和使用
    LRU Cache java实现
    HTTP长连接、短连接使用及测试
    mac下redis安装、设置、启动停止
    一次SocketException:Connection reset 异常排查
    【swift 结构体】
    【swift array 数组】
    【iOS知识汇】NSNotification
  • 原文地址:https://www.cnblogs.com/Khada-Jhin/p/9188058.html
Copyright © 2011-2022 走看看