AC自动机初学

      ac自动机,用于解决而AC自动机解决的是长文本的多模板匹配问题而KMP算法专门解决长文本的单模板匹配问题,字典树专门解决单个单词(短文本)多模板匹配问题。所以这里实际上就是KMP+字典树。

      首先对于多个短单词,最好的存储办法——字典树。(这里是没有malloc()、free()的模板,几乎一样,只是用结构体的构造、析构函数取代了)。

      然后KMP的话,肯定设计到一个next【】数组的问题了,在这里称之为失败指针,在结构体中新增 Node *fail指针来表示。求法在代码中体现。

     最后getKMP()函数,就是相应的KMP部分了。

静态模板(改饶齐版,last数组优化版)

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int MAXNode=11000;
const int SIZE=26;
struct AC_Automaton
{
    int ch[MAXNode][SIZE];
    int val[MAXNode];  // 代表单词节点
    int fail[MAXNode]; // fail数组
    int last[MAXNode]; // last数组 —— fail的优化!!!
    // last[i]=j :j节点表示的单词是i节点单词的后缀,且j节点是单词节点
    int sz;//节点个数
    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 len=strlen(s);
        int u=0;
        for(int i=0; i<len; 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;
    }
    //build_AC_Automaton函数负责构造fail和last数组
    void build_AC_Automaton()
    {
        int q[MAXNode];
        int head=0, tail=0;
        last[0]=fail[0]=0;
        for(int i=0; i<SIZE; i++)//初始化root的儿子
        {
            int u=ch[0][i];
            if(u)
            {
                fail[u]=last[u]=0;//
                q[tail++]=u;
            }
        }
        while(head<tail)// 按BFS顺序计算fail
        {
            int now=q[head++];
            for(int i=0; i<SIZE; i++)
            {
                int u=ch[now][i];//儿子u
                if(u==0)//该儿子不存在,不考虑
                {
                    //ch[now][i]=ch[fail[now]][i];//连全部的边,Trie图
                    continue;
                }
                q[tail++]=u;//入队
                int v=fail[now];
                while(v && ch[v][i]==0)//一直找失败指针
                    v=fail[v];
                fail[u]=ch[v][i];//求得 fail【u】
                if(val[fail[u]]) //求得 last【u】
                    last[u]=fail[u];
                else//如果 点fail[u] 不是单词结束点
                    last[u]=last[fail[u]];
            }
        }
    }
    //递归打印与结点i后缀相同的前缀节点编号
    //进入函数条件:val[i]>0
    void print(int p)
    {
        if(p)
        {
            print(last[p]);
        }
    }
    // 在s中找出 出现了哪几个模板单词
    void search(char *str)
    {
        int len=strlen(str);
        int p=0;
        for(int i=0; i<len; i++)
        {
            int id=str[i]-'a';             //第一步:更新p
            while(p!=0 && ch[p][id]==0)
            //跳出条件:①next【id】!=0, ②p=0
            {
                p=fail[p];
            }
            p=ch[p][id];
            if(val[p])                   //第二步:利用p
                print(p);
            else if(last[p])
                print(last[p]);
        }
    }
}ac;
int main()
{
    ac.init();

    return 0;
}




动态模板(自改,并没有本质不同,更顺手了):

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
const int MAXN=1000010;
const int MAXM=51;
const int SIZE=26; //26个小写字母
int sz;
struct Node
{
    Node *fail;  //失败指针
    Node *next[SIZE]; //儿子结点个数
    int count; //单词个数
    //int num;   //节点编号  随sz++
    Node()
    {
        fail=NULL;
        count=0;
        //num=sz++;
        memset(next, 0, sizeof(next));
    }
    ~Node()
    {
        delete next;
    }
}*root;
Node *q[MAXN/2];
void Insert(Node *root, char *s)
{
    int len=strlen(s);
    Node *p=root;
    for(int i=0; i<len; i++)
    {
        int id=s[i]-'a';
        if(p->next[id]==NULL)
            p->next[id]=new Node();//调用构造函数
        p=p->next[id];
    }
    p->count++; //在单词的最后一个结点count + 1,代表一个单词
}
void build_AC_Automaton(Node *root)
{

    root->fail=NULL;
    int head=0, tail=0;
    for(int i=0; i<SIZE; i++)//先初始化root的儿子
    {
        if(root->next[i]!=NULL)//儿子存在
        {
            root->next[i]->fail=root;//失败指针为root
            q[tail++]=root->next[i];
        }
    }
    while(head<tail)
    {
        Node *temp=q[head++];
        for(int i=0; i<SIZE; i++)
        {
            if(temp->next[i]!=NULL)//儿子存在
            {

                Node *p=temp->fail;//
                while(p!=NULL)
                {
                    if(p->next[i]!=NULL)//如果失败指针有i儿子,
                    {
                        temp->next[i]->fail=p->next[i];//求得当前点i儿子的失败指针,
                        break;      // 并退出
                    }
                    p=p->fail;//否则,更新失败指针
                }
                if(p==NULL)
                    temp->next[i]->fail=root;
                q[tail++]=temp->next[i];
                //cout<<temp->next[i]->num<<" fa "<<temp->next[i]->fail->num<<endl;
                /*            //错误代码!必须要用上面while循环一直找才行
                if(p->next[i]!=NULL)
                {
                    temp->next[i]->fail=p->next[i];
                }
                else if(p->next[i]==NULL)
                {
                    temp->next[i]->fail=root;
                }
                if(p==NULL)
                    temp->next[i]->fail=root;
                */
            }
        }
    }
}
/*         //对应饶齐静态模板 print()函数,与下面while()互换 (利用p)
int print(Node *temp)
{
    if(temp!=root && temp->count)
    {
        printf("%d\n", temp->count);
        print(temp->fail);
    }
}
*/
int Search(Node *root, char *str)
{
    int cnt=0;
    int len=strlen(str);
    Node *p=root;//从根节点开始
    for(int i=0; i<len; i++)
    {
        int id=str[i]-'a';            //---------------第一步:更新p
        while(p->next[id]==NULL && p!=root)//更新p
        //跳出条件:①next【i】!=NULL, ②p=root
        {
            p=p->fail;//p=p的失败指针
        }
        p=p->next[id];//next【i】儿子
        if(p==NULL)//该儿子不存在,p=root
            p=root;
        Node *temp=p;                  //---------------第二步:利用p
        while(temp!=root && temp->count!=-1)
        //跳出条件:①temp=root, ②coumt=-1(访问过)
        {
            cnt+=temp->count;
            temp->count=-1;
            temp=temp->fail;
        }
        //cout<<"i= "<<i<<"  cnt="<<cnt<<endl;
    }
    return cnt;
}
int main()
{
    int N, T;
    char keyword[MAXM]; //单词
    char str[MAXN]; //模式串  文章
    scanf("%d", &T);
    while(T--)
    {
        scanf("%d", &N);
        getchar();
        //sz=0;
        root=new Node();
        while(N--)
        {
            scanf("%s", keyword);
            Insert(root, keyword);
        }
        build_AC_Automaton(root);
        scanf("%s", str);
        printf("%d\n", Search(root, str));
    }
    return 0;
}




模板(初始):

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
const int MAXN=1000010; //模式串的最大长度MAXN - 1
const int MAXM=51; //单词最大长度为MAXM - 1
const int SIZE=26; //26个小写字母
struct Node
{
    Node *fail;  //失败指针
    Node *next[SIZE]; //儿子结点个数
    int count; //单词个数
    Node()
    {
        fail=NULL;
        count=0;
        memset(next, 0, sizeof(next));
    }
    ~Node()
    {
        delete next;
    }
}*q[MAXN/2];
void Insert(Node *root, char *s)
{
    int len=strlen(s);
    Node *p=root;
    for(int i=0; i<len; i++)
    {
        int id=s[i]-'a';
        if(p->next[id]==NULL)
            p->next[id]=new Node();//调用构造函数
        p=p->next[id];
    }
    p->count++; //在单词的最后一个结点count + 1,代表一个单词
}
void build_AC_Automaton(Node *root)
{
    root->fail=NULL;
    int head, tail;
    head=tail=0;
    q[tail++]=root;//根节点入队
    while(head<tail)
    {
        Node *temp=q[head++];//队首元素出队
        for(int i=0; i<SIZE; i++)//temp的每个儿子   i就是当前儿子的字母
        {
            if(temp->next[i]!=NULL)//如果这个儿子存在的话
            {
                if(temp == root)//temp为根节点 初始化
                {
                    temp->next[i]->fail=root;
                }
                else//非根
                {
                    Node *p=temp->fail;//temp的失败指针
                    while(p!=NULL)
                    {
                        if(p->next[i]!=NULL)//如果失败指针的next【i】非空
                        {
                            temp->next[i]->fail=p->next[i];//更新儿子的失败指针,
                            break;//并跳出
                        }
                        p=p->fail;//沿着 失败指针 向上找
                    }
                    if(p==NULL)
                        temp->next[i]->fail=root;
                }
                q[tail++]=temp->next[i];//该儿子next【i】入队
            }
        }
    }
}
int query(Node *root, char *str)
{
    int cnt=0;
    int len=strlen(str);
    Node *p=root;
    for(int i=0; i<len; i++)
    {
        int id=str[i]-'a';
        while(p->next[id]==NULL && p!=root)
        //跳出条件:①next【i】!=NULL, ②p=root
        {
            p=p->fail;//p=p的失败指针
        }
        p=p->next[id];//next【i】儿子
        //p = (p == NULL) ? root : p;
        if(p==NULL)//该儿子不存在,p=root
            p=root;
        Node *temp=p;
        while(temp!=root && temp->count!=-1)
        //跳出条件:①temp=root, ②coumt=-1(访问过)
        {
            cnt+=temp->count;
            temp->count=-1;
            temp=temp->fail;
        }
        //cout<<"i= "<<i<<"  cnt="<<cnt<<endl;
    }
    return cnt;
}
int main()
{
    int N, T;
    Node *root;
    char keyword[MAXM]; //单词
    char str[MAXN]; //模式串  文章
    scanf("%d", &T);
    while(T--)
    {
        scanf("%d", &N);
        getchar();
        root=new Node();
        while(N--)
        {
            scanf("%s", keyword);
            Insert(root, keyword);
        }
        build_AC_Automaton(root);
        scanf("%s", str);
        printf("%d\n", query(root, str));
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值