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上之后再递归儿子,这是一个小小的优化。

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

posted @ 2018-08-16 19:36  Yang1208  阅读(585)  评论(1编辑  收藏  举报