码迷,mamicode.com
首页 > 其他好文 > 详细

AC自动机

时间:2015-04-24 06:36:56      阅读:166      评论:0      收藏:0      [点我收藏+]

标签:

学习ac自动机需要先会kmp和trie:kmp | trie

终于入门了,看了一天,现在整理一下。

算法简介:

AC自动机主要用于解决多模式串的匹配问题。

如hdu2222:

给定N(N <= 10000)个长度不大于50的模式串,再给定一个长度为L(L <= 106)目标串,求目标串出现了多少个模式串。

模式串: she he say shr her  目标串:yasherhs  目标串中出现的模式串一共有3个,分别为 she he her

算法步骤:

1.先用模式串建一个字典树

2.对字典序中的节点构建失配指针,构成trie图

3.用目标串进行查询

 

构建字典树

这点不多说了,详见字典树

void creatTrie(Trie *root,char *str)
{
    Trie *p=root;
    p=root;
    for(int i=0;str[i];i++)
    {
        int id=charToNum(str[i]);
        if(id==-1)
        {
            puts("error");
            return;
        }
        if(p->next[id]==NULL)
            p->next[id]=new Trie();
        p=p->next[id];
        p->v++;
    }
    p->cnt++;
}

失配指针

失配指针的含义

一个模式串的某个字符匹配失败的时候,就跳到它的失败指针上继续匹配,重复上述操作,直到这个字符匹配成功,所以失败指针一定满足一个性质,它指向的一定是某个串的前缀,并且这个前缀是当前结点所在前缀的后缀,而且一定是最长后缀。仔细理解一下这句话,首先,一定是某个串的前缀,这是显然的,因为trie树本来就是前缀树,它的任意一个结点都是某个模式串的前缀;然后再来看后面一句话,为了让当前字符能够找到匹配,那么当前结点的某个后缀必须要和某个模式串的前缀相匹配,这个性质就和KMP的next数组不谋而合了。

如何利用BFS求出所有结点的失败指针

  1) 对于根结点root的失败指针,我们将它直接指向NULL,对于根结点下所有的子结点,失败指针一定是指向root的,因为当一个字符都不能匹配的时候,自然也就不存在更短的能够与之匹配的前缀了;

  2) 将求完失败指针的结点插入队列中;

  3) 每次弹出一个结点now,询问它的每个字符对应的子结点,为了阐述方便,我们将now的i号子结点记为now->next[i]:

         a) 如果now->next[i]为NULL,那么将now->next[i]指向now的失败指针的i号子结点, 即 now->next[i] = now->fail->next[i];

         b) 如果now->next[i]不等于NULL,则需要构造now->next[i]的失败指针,由于a)的操作,我们知道now的失败指针一定存在一个i号子结点,即now->fail->next[i],那么我们将now->next[i]的失败指针指向它,即now->next[i]->fail = now->fail->next[i];

  4) 重复2)的操作直到队列为空;

void build_ac(Trie *root)
{
    Trie *p,*tmp;
    int head=0,tail=0;
    q[tail++]=root;
    while(head!=tail)
    {
        p=q[head++];
        for(int i=0;i<maxn;i++)
        {
            tmp=(p==root)?root:p->fail->next[i];
            if(p->next[i]==NULL){
                p->next[i]=tmp;
            }
            else{
                p->next[i]->fail=tmp;
                q[tail++]=p->next[i];
            }
        }
    }
}

目标串匹配

对目标串进行匹配的时候,同样需要扫描目标字符串。由于trie图已经创建完毕,每个结点读入一个字符的时候都能够进入到下一个状态,所以我们只需要根据目标串给定的字符进行遍历,然后每次检查当前的结点是否是结尾结点,当然还需要检查p的失败指针指向的结点...累加所有的cnt和即为模式串的个数。

int query_ac(Trie *root,char *str)
{
    int cnt=0;
    Trie *p=root,*tmp=NULL;
    for(int i=0;str[i];i++)
    {
        int id=charToNum(str[i]);
        if(id==-1)
        {
            p=root;
            continue;
        }
        p=p->next[id];
        tmp=p;
        while(tmp!=root&&tmp->cnt!=-1)
        {
            if(tmp->cnt)
            {
                cnt+=tmp->cnt;
                tmp->cnt=-1;
            }
            tmp=tmp->fail;
        }
    }
    return cnt;
}

 

hdu2222

1.构建字典树

2.构造失败指针

用BFS来构造失败指针,与KMP算法相似的思想。

首先,root入队,第1次循环时处理与root相连的字符,也就是各个单词的第一个字符h和s,因为第一个字符不匹配需要重新匹配,所以第一个字符都指向root(root是Trie入口,没有实际含义)失败指针的指向对应下图中的(1),(2)两条虚线;

第2次进入循环后,从队列中先弹出h,接下来p指向h节点的fail指针指向的节点,也就是root;p=p->next[i], 则把节点e的fail指针指向root表示没有匹配序列,对应图-2中的(3),然后节点e进入队列;

第3次循环时,弹出的第一个节点a的操作与上一步操作的节点e相同,把a的fail指针指向root,对应图-2中的(4),并入队;

第4次进入循环时,弹出节点h(图中左边那个),这时操作略有不同。由于p->next[i]!=NULL(root有h这个儿子节点,图中右边那个),这样便把左边那个h节点的失败指针指向右边那个root的儿子节点h,对应图-2中的(5),然后h入队。以此类推:在循环结束后,所有的失败指针就是图-2中的这种形式。

技术分享

3.扫描

构造好Trie和失败指针后,我们就可以对主串进行扫描了。这个过程和KMP算法很类似,但是也有一定的区别,主要是因为AC自动机处理的是多串模式,需要防止遗漏某个单词,所以引入temp指针。

匹配过程分两种情况:(1)当前字符匹配,表示从当前节点沿着树边有一条路径可以到达目标字符,此时只需沿该路径走向下一个节点继续匹配即可,目标字符串指针移向下个字符继续匹配;(2)当前字符不匹配,则去当前节点失败指针所指向的字符继续匹配,匹配过程随着指针指向root结束。重复这2个过程中的任意一个,直到模式串走到结尾为止。

 对照上图,看一下模式匹配这个详细的流程,其中模式串为yasherhs。对于i=0,1。Trie中没有对应的路径,故不做任何操作;i=2,3,4时,指针p走到左下节点e。因为节点e的count信息为1,所以cnt+1,并且讲节点e的count值设置为-1,表示改单词已经出现过了,防止重复计数,最后temp指向e节点的失败指针所指向的节点继续查找,以此类推,最后temp指向root,退出while循环,这个过程中count增加了2。表示找到了2个单词she和he。当i=5时,程序进入第5行,p指向其失败指针的节点,也就是右边那个e节点,随后在第6行指向r节点,r节点的count值为1,从而count+1,循环直到temp指向root为止。最后i=6,7时,找不到任何匹配,匹配过程结束。

 

注意:AC自动机建立的过程改变了字典序原有的next[],所以不能再用字典序中的方法查询和释放内存

技术分享
#include<cstdio>
#include<cstring>
#include<string>
#include<iostream>
#include<algorithm>
using namespace std;
#define maxn 26
#define N 500010 //定义队列大小
struct Trie
{
    Trie *next[maxn];
    Trie *fail;
    int cnt;//结尾标记,统计以该节点结尾的单词个数
    int v;//前缀标记,统计以该节点前的串为前缀的单词个数
    Trie()
    {
        fail=NULL;
        cnt=0;
        v=0;
        for(int i=0;i<maxn;i++)
            next[i]=NULL;
    }
}*q[N];
int charToNum(char s)
{
    if(s>=a&&s<=z)return s-a;
    return -1;
}
void creatTrie(Trie *root,char *str)
{
    Trie *p=root;
    p=root;
    for(int i=0;str[i];i++)
    {
        int id=charToNum(str[i]);
        if(id==-1)
        {
            puts("error");
            return;
        }
        if(p->next[id]==NULL)
            p->next[id]=new Trie();
        p=p->next[id];
        p->v++;
    }
    p->cnt++;
}
void build_ac(Trie *root)
{
    Trie *p,*tmp;
    int head=0,tail=0;
    q[tail++]=root;
    while(head!=tail)
    {
        p=q[head++];
        for(int i=0;i<maxn;i++)
        {
            tmp=(p==root)?root:p->fail->next[i];
            if(p->next[i]==NULL){
                p->next[i]=tmp;
            }
            else{
                p->next[i]->fail=tmp;
                q[tail++]=p->next[i];
            }
        }
    }
}
int query_ac(Trie *root,char *str)
{
    int cnt=0;
    Trie *p=root,*tmp=NULL;
    for(int i=0;str[i];i++)
    {
        int id=charToNum(str[i]);
        if(id==-1)
        {
            p=root;
            continue;
        }
        p=p->next[id];
        tmp=p;
        while(tmp!=root&&tmp->cnt!=-1)
        {
            if(tmp->cnt)
            {
                cnt+=tmp->cnt;
                tmp->cnt=-1;
            }
            tmp=tmp->fail;
        }
    }
    return cnt;
}
char qu[1000010];
int main()
{
    int T,n;
    scanf("%d",&T);
    char str[50];
    while(T--)
    {
        Trie *root=new Trie();
        scanf("%d",&n);
        while(n--)
        {
            scanf("%s",&str);
            creatTrie(root,str);
        }
        build_ac(root);
        scanf("%s",qu);
        printf("%d\n",query_ac(root,qu));
      //  Delete(root); AC自动机不能这么释放内存
    }
    return 0;
}
View Code

 

 

参考:

http://www.cppblog.com/menjitianya/archive/2014/07/10/207604.html

http://blog.csdn.net/niushuai666/article/details/7002823

http://blog.csdn.net/niushuai666/article/details/7002736

 

AC自动机

标签:

原文地址:http://www.cnblogs.com/kylehz/p/4450760.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!