AC自动机

来源:互联网 发布:征服者更新用什么网络 编辑:程序博客网 时间:2024/06/08 10:53

AC自动机

         直接学AC自动机比较难理解,强烈建议先学完KMP和字典树并进行一定的练习后,对于失配指针和字典树构造有一定理解后再来学AC自动机的内容。有关AC自动机的详细介绍可见刘汝佳的《算法竞赛入门经典训练指南》P214.

给你一个字典(包含n个不重复的单词),然后给你一串连续的字符串文本(长为len),问你该文本里面的哪些位置正好出现了字典中的某一个或某几个单词?输出这些位置以及出现的单词。

         这个问题可以用n个单词的n次KMP算法来做(效率为O(n*len*单词平均长度)),也可以用1个字典树去匹配文本串的每个字母位置来做(效率为O(len*每次字典树遍历的平均深度))。上面两种解法效率都不高,如果用AC自动机来解决的话,效率将为线性O(len)时间复杂度。(上述3种时间效率并未把构建KMP,字典树以及AC自动机的时间算上)

         KMP算法专门解决长文本的单模板匹配问题,字典树专门解决单个单词(短文本)多模板匹配问题。而AC自动机解决的是长文本的多模板匹配问题。且AC自动机不但时间上具有优势,空间上也颇具优势。

模板代码如下:

(参考博客 点击打开链接)

#include<cstdio>#include<cstring>#include<queue>using namespace std;const int maxnode=11000;const int sigma_size=26;struct AC_Automata{    int ch[maxnode][sigma_size];    int val[maxnode];   // 每个字符串的结尾结点都有一个非0的val    int f[maxnode];     // fail函数    int last[maxnode];  // last[i]=j表j节点表示的单词是i节点单词的后缀,且j节点是单词节点    int sz;    //初始化0号根节点的相关信息    void init()    {        sz=1;        memset(ch[0],0,sizeof(ch[0]));        val[0]=0;    }    //insert负责构造ch与val数组    //插入字符串,v必须非0表示一个单词节点    void insert(char *s,int v)    {        int n=strlen(s),u=0;        for(int i=0; i<n; i++)        {            int id=s[i]-'a';            if(ch[u][id]==0)            {                ch[u][id]=sz;                memset(ch[sz],0,sizeof(ch[sz]));                val[sz++]=0;            }            u=ch[u][id];        }        val[u]=v;    }    //getFail函数负责构造f和last数组    void getFail()    {        queue<int> q;        last[0]=f[0]=0;        for(int i=0; i<sigma_size; i++)        {            int u=ch[0][i];            if(u)            {                f[u]=last[u]=0;                q.push(u);            }        }        while(!q.empty())// 按BFS顺序计算fail        {            int r=q.front(); q.pop();            for(int i=0; i<sigma_size; i++)            {                int u=ch[r][i];                if(u==0)continue;                q.push(u);                int v=f[r];                while(v && ch[v][i]==0) v=f[v];                f[u]= ch[v][i];                last[u] =  val[f[u]]?f[u]:last[f[u]];            }        }    }    //递归打印与结点i后缀相同的前缀节点编号    //进入此函数前需保证val[i]>0    void print(int i)    {        if(i)        {            printf("%d\n",i);            print(last[i]);        }    }    // 在s中找出 出现了哪几个模板单词    void find(char *s)    {        int n=strlen(s),j=0;        for(int i=0; i<n; i++)        {            int id=s[i]-'a';            while(j && ch[j][id]==0) j=f[j];            j=ch[j][id];            if(val[j]) print(j);            else if(last[j]) print(last[j]);        }    }};AC_Automata ac;


原创粉丝点击