AC自动机部分讲解

首先结构体的建立

struct node
{
    node *fail;// fail 指针
    node *nex[26];//字典树标配,26个字母
    int cnt;//是否为该单词的最后一个节点
    node()//构建函数
    {
        fail = NULL;
        for(register int i=0;i<26;i++)nex[i]=NULL;
        cnt = 0;
    }
}q[maxn];//队列,方便用于bfs构造失败指针
int head,tail; // 队列头尾
char key[100],s[maxn];//查找词和模板串

构建完成字典树后图片如下 

之后就是插入函数。。。

inline void insert(char *s, node *rt)
{
    node *p = rt;
    int i=0, index;
    while(s[i])
    {
        index = s[i] - 'a';//找到字母编号
        if(p->nex[index] == NULL)p->nex[index]=new node();
        p = p->nex[index];
        i ++;
    }
    p->cnt ++;//在单词的最后一个节点count+1,代表一个单词
}

 在构造完这棵Tire之后,接下去的工作就是构造下失败指针。构造失败指针的过程概括起来就一句话:

设这个节点上的字母为C,沿着他父亲的失败指针走,直到走到一个节点,他的儿子中也有字母为C的节点。然后把当前节点的失败指针指向那个字母也为C的儿子。如果一直走到了root都没找到,那就把失败指针指向root。具体操作起来只需要:先把root加入队列(root的失败指针指向自己或者NULL),这以后我们每处理一个点,就把它的所有儿子加入队列,队列为空。

                                              --------摘自http://www.cppblog.com/mythit/archive/2009/04/21/80633.html

非常的像kmp的 next 数组,这里就fail指针也可以理解成,如果单词匹配到这里失败了,返回最近使用过当前字母 c 的节点,这样就可以继续去匹配了。。。缩短时间了,。。

inline void build(node *rt)
{
    rt->fail = NULL;//根节点的 fail 为空
    q[head++] = rt;//入队
    while(head != tail)//用 bfs 思路不断找到最近的fail
    {
        node *temp = q[tail++], *p = NULL;//取队首
        for(register int i=0;i<26;i++)
        {
            if(temp->nex[i] != NULL)//如果这个字母出现过
            {
                if(temp == rt)temp->nex[i]->fail = rt;
                else
                {
                    p = temp->fail;
                    while(p != NULL)
                    {
                        if(p->nex[i] != NULL)
                        {
                            temp->nex[i]->fail = p->nex[i];
                            break;
                        }
                    }
                    if(p == NULL)temp->nex[i]->fail = rt;
                }
                q[head++] = temp->nex[i];
            }
        }
    }
}

 从代码观察下构造失败指针的流程:对照图-2来看,首先root的fail指针指向NULL,然后root入队,进入循环。第1次循环的时候,我们需要处理2个节点:root->next[‘h’-‘a’](节点h) 和 root->next[‘s’-‘a’](节点s)。把这2个节点的失败指针指向root,并且先后进入队列,失败指针的指向对应图-2中的(1),(2)两条虚线;第2次进入循环后,从队列中先弹出h,接下来p指向h节点的fail指针指向的节点,也就是root;进入第13行的循环后,p=p->fail也就是p=NULL,这时退出循环,并把节点e的fail指针指向root,对应图-2中的(3),然后节点e进入队列;第3次循环时,弹出的第一个节点a的操作与上一步操作的节点e相同,把a的fail指针指向root,对应图-2中的(4),并入队;第4次进入循环时,弹出节点h(图中左边那个),这时操作略有不同。在程序运行到14行时,由于p->next[i]!=NULL(root有h这个儿子节点,图中右边那个),这样便把左边那个h节点的失败指针指向右边那个root的儿子节点h,对应图-2中的(5),然后h入队。以此类推:在循环结束后,所有的失败指针就是图-2中的这种形式。

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

简单总结一下就是,完全模拟kmp,如果当前匹配失败了,返回最近的重合度最高的前缀部分,当然如果连续回退到根节点,那么显而易见,匹配失败了。。。。你看这个图里很显然, he这个小的字符串重复出现了,搜索的时候可以利用这点。

inline int query(node *rt)
{
    int i=0,ct=0,index,len=strlen(s);
    node *p = rt;
    while(s[i])
    {
        index = s[i]-'a';
        while(p->nex[index]==NULL && p!=rt) p=p->fail;
        p = p->nex[index];
        p = p==NULL ? rt:p;
        node *temp = p;
        while(temp!=rt && temp->cnt!=-1)
        {
            ct += temp->cnt;
            temp->cnt = -1;
            temp = temp->fail;
        }
        i++;
    }
    return ct; 
}

附加一个优秀博客地址 https://blog.youkuaiyun.com/creatorx/article/details/71100840

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值