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

    AC自动机讲解

      首先,作为作者的我一定知道你已经会了这两个算法:KMP与Trie树,如若不会,可以先学习一下。

      我在这里声明一下AC自动机不是自动AC的算法,其全称是Aho-Corasick automaton,是一种著名的多模匹配算法。其实现类似于Trie树与KMP算法的结合,是将多个模式串放在Trie树上做出fail指针,并用文本串进行匹配。整个算法共分为三个部分:1) 建Trie树 2) 匹配fail指针 3) 运用并求出答案。下面我会讲解前两个步骤。

    建Trie树

      相信学过Trie树的OIer都会,就是简单的动态开点,如果当前节点的出边有下一个字符,则直接走下去,否则我们可以新开一个节点,再走下去,是不是和动态开点线段树思想一样?代码也比较好实现,在这里我以都是小写英文字母为例。

    #define N 1000001
    char str;int root,cnt;
    struct Node
    {int son[26];}node[N];        //每一个结构体都是一个节点,son数组是当前节点的儿子们
    void build(int now,int p)
    {
    	if(now>=lenth) return;     //当遍历结束当前字符串之后是要返回的,不能继续遍历下去,记住字符串是从0开始的
    	int s=str[now]-'a';
    	if(!node[p].son[s]) node[p].son[s]=++cnt;    //如果没有节点,新开一个节点就好了
    	build(now+1,node[p].son[s]);             //继续遍历
    }
    int main()
    {
    	scanf("%s",str),lenth=strlen(str),build(0,root);
    }
    

    匹配fail指针

      这是整个算法的核心部分,也是我要重点讲解的部分,我们看一下下面左面的图片,这是一棵已经建好的Trie树,分别是五个模式串所建成的Trie树(五个模式串为:aaa、abc、bcd、cab、cad),我们就要用这个简单的Trie树来学习什么是fail指针。首先我们要弄明白fail指针的定义,AC自动机的fail指针和KMP中的fail指针大致相同,但是还是有一定的区别的,这里的fail指针指向的是一个字符串中的一个点,并且当前字符串的后缀与以指向的点为结尾的后缀相同的长度最长,见下面右图中,已经给出例子来了,理解一下定义。

      那么它是怎么匹配出来的呢?我们发现每一个点的fail指针都是指向上面,即层数小于自己的点,所以我们不能用DFS来更新,应该用BFS这种优秀的算法,这样我们每一次更新点的时候,都能用已经更新好点,我们再看一看图,发现当前点的fail的儿子与自己的儿子有相同时,自己的儿子的fail就是自己的fail的儿子,图中有不只一组满足这个性质,根据这个性质,我们寻找fail指针就容易多了。下面就是代码部分了。

    void find_fail()
    {
        queue <int> q;
        for(int i=1;i<=26;i++)
            if(node[root].son[i])
                q.push(node[root].son[i]);
        while(!q.empty())
        {
            int p=q.front();q.pop();
            for(int i=1;i<=26;i++)
            {
                if(node[p].son[i])
                {
                    node[node[p].son[i]].fail=node[node[p].fail].son[i];
                    q.push(node[p].son[i]);
                }
                else node[p].son[i]=node[node[p].fail].son[i];
            }
        }
    }
    

      在这里,我用的是系统之中的队列,如果有兴趣手写的话,可以手写,我不拦着。

      有人看过代码,可能会问,那个else是什么鬼?在这里我解释一下,如果当前节点的fail有自己没有的儿子,我们可以把fail的儿子接在自己上,这样在更新答案时,就可以直接递归的找下去,不用再跳到fail上之后再递归儿子,这是一个小小的优化。

      剩下的就是应用了,应用就不细说了,具体题目具体分析,不会的可以发评论问我。

  • 相关阅读:
    使用 requests 维持会话
    使用 requests 发送 POST 请求
    使用 requests 发送 GET 请求
    requests 安装
    使用 urllib 分析 Robots 协议
    使用 urllib 解析 URL 链接
    使用 urllib 处理 HTTP 异常
    使用 urllib 处理 Cookies 信息
    使用 urllib 设置代理服务
    按单生产程序发布
  • 原文地址:https://www.cnblogs.com/yangsongyi/p/9481818.html
Copyright © 2011-2022 走看看