AC-自动机的痛苦学习经历

  学习之后就要做好笔记嘛,PS:感冒好了之后,脑袋灵光了不少啊。。。。

  前几天学习了KMP算法,又复习了字典树,都是为了AC-自动机,这个神奇的东西,KMP是单字符串匹配,而AC-自动机是在Tire树的情况下,进行的多字符匹配的,关键在于next数组的求解,只要理解了next数组的意义呢,AC自动机也就好搞了,首先贴下两篇关于AC自动机的学习资料


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

http://hi.baidu.com/nialv7/item/ce1ce015d44a6ba7feded52d


这两篇博文都是非常好的关于自动机匹配函数的理解,其实自动机由建树,建立匹配函数,加查找这三部分构成。

AC自动机的构造:

1.构造一棵Trie,作为AC自动机的搜索数据结构。

2.构造fail指针,使当前字符失配时跳转到具有最长公共前后缀的字符继续匹配。如同 KMP算法一样, AC自动机在匹配时如果当前字符匹配失败,那么利用fail指针进行跳转。由此可知如果跳转,跳转后的串的前缀,必为跳转前的模式串的后缀并且跳转的新位置的深度(匹配字符个数)一定小于跳之前的节点。所以我们可以利用 bfs在 Trie上面进行 fail指针的求解。

3.扫描主串进行匹配。

直接摘录下来了,这就是自动机的具体构造方法。然后我再贴下,我按照kuangbin巨巨写下的模板,并且对模板进行详细的解释。

#include<iostream>
#include<cstdio>
#include<string.h>
#include<string>
#include<set>
#include<algorithm>
#include<cmath>
#include<queue>


#define ll __int64
#define MAX 1000009


const int maxnode = MAX;
const int sigma_size = 26;
using namespace std;


struct Trie
{
    int ch[maxnode][sigma_size];
    int val[maxnode];
    int next[maxnode];
    int sz,root;
    int idx(char c)//编号函数
    {
        return c - 'a';
    }
    int newnode()//建立新的结点,跟以前写的不一样,但是我觉得这种写法更好一些
    {
        for(int i = 0; i<26; i++)
            ch[sz][i] = 0;
        val[sz++] = 0;
        return sz - 1;
    }
    void init()//初始化
    {
        sz = 0;
        root = newnode();
    }
    void Insert(char* s)//插入
    {
        int u = root;
        int n = strlen(s);
        for(int i = 0; i<n; i++)
        {
            int c = idx(s[i]);
            if(!ch[u][c])
                ch[u][c] = newnode();
            u = ch[u][c];
        }
        val[u]++;
    }
    /*
    Trie树上的失败指针与KMP的next数组类似:
        假设有一个节点k,他的失败指针指向j。那么k,j满足这个性质:设root到j的距离为n,
        则从k之上的第n个节点到k所组成的长度为n的单词,与从root到j所组成的单词相同。
    建立失配数组:
        对于每个节点,我们可以这样处理:设这个节点上的字母为C,沿着他父亲的失败指针走,
        直到走到一个节点,他的儿子中也有字母为C的节点。然后把当前节点的失败指针指向那个字目也为C的儿子。如果一直走到了root都没找到,
        那就把失败指针指向root最开始,我们把root加入队列(root的失败指针显然指向自己),
        这以后我们每处理一个点,就把它的所有儿子加入队列,直到搞完。
    DarkRaven原创


   匹配过程分两种情况:
   (1)当前字符匹配,表示从当前节点沿着树边有一条路径可以到达目标字符,此时只需沿该路径走向下一个节点继续匹配即可,目标字符串指针移向下个字符继续匹配;
   (2)当前字符不匹配,则去当前节点失败指针所指向的字符继续匹配,匹配过程随着指针指向root结束。重复这2个过程中的任意一个,直到模式串走到结尾为止。
    */
    void build()//建立next数组
    {
        queue<int>Q;
        next[root] = root;
        for(int i = 0; i<26; i++)
        {
            if(!ch[root][i])
            {
                ch[root][i] = root;
            }
            else
            {
                next[ch[root][i]] = root;;//第一个字符不需要匹配,所以都指向root
                Q.push(ch[root][i] );//先入队rppt
            }
        }
        while(!Q.empty())
        {
            int now = Q.front();//取队首
            cout<<now<<endl;
            Q.pop();
            for(int i = 0; i<26; i++)
            {
                if(!ch[now][i])
                {
                    ch[now][i] = ch[next[now]][i];//
                }
                else
                {
                    next[ch[now][i]] = ch[next[now]][i];
                    Q.push(ch[now][i]);
                }
            }
        }
    }
    int query(char* s)//扫描查询
    {
        int len = strlen(s);
        int now = root;
        int res = 0;
        for(int i = 0; i<len; i++)
        {
            int c = idx(s[i]);
            now = ch[now][c];
            int temp = now;
            while(temp!= root)
            {
                res+=val[temp];
                val[temp] = 0;
                temp = next[temp];
            }
        }
        return res;
    }
    void debug()//找bug
    {
        for(int i = 0; i<sz; i++)
        {
            printf("id = %3d,next = %3d,val = %3d,chi = [",i,next[i],val[i]);
            for(int j = 0; j < 26; j++)
                printf("%2d",ch[i][j]);
            printf("]\n");
        }
    }
};


char str[MAX];
Trie Ac;


int main()
{
    int T;
    int n;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d",&n);
        Ac.init();
        for(int i = 0; i<n; i++)
        {
            scanf("%s",str);
            Ac.Insert(str);
        }
        Ac.build();
        scanf("%s",str);
       // Ac.debug();
        printf("%d\n",Ac.query(str));
    }
    return 0;
}


你可能感兴趣的:(AC-自动机的痛苦学习经历)