• AC自动机基础入门讲解(KMP与Tire树的结合)


    什么是AC自动机?

    ​ AC自动机并不是自动AC机(●’◡’●),Aho–Corasick算法是由Alfred V. Aho和Margaret J.Corasick 发明的字符串搜索算法.

    他能做什么?

    ​ 在文本串上匹配多模式串,常用的用于计算文本串上出现了多少个模式串。

    他是怎么做的?

    ​ 与KMP算法相似,用fail指针指向失配时的下一个匹配位置。不同的是KMP只适用于单模式串,AC自动机可以匹配多个模式串. AC自动机的基础知识有字典树,KMP(希望把KMP真正理解懂再来搞这个东西)

    fail指针是怎么做的?

    ​ 在KMP算法中next指针(fail指针)实际是找出该字串的最长后缀匹配,在AC自动机中每个节点的fail指针指向该节点对应字符串的最长后缀节点(这个节点不能为本身,意思与Kmp的next指针相似)

    下面是一些单词 ,对这些单词创建字典树

    abc bc abcd aef cACTree.png

    接下里就创建fail指针,首先fail的意义跟Kmp算法中next的意义相同,都指向该字串的最长后缀。

    怎么创建fail指针呢?

    首先按照fail指针的意义,root的所有直接儿子的fail指针都指向root, Why? 因为root直接儿子的节点代表的字符长度都为1,且字符不重复,又因为fail指针的意义为指向最长后缀字符串的节点(非本身),所以这一步也就合情合理了吧。

    接下来,对于每个节点,要找该节点的fail指针(最长匹配)怎么找呢?

    是不找是这个节点的父亲的fail所代表节点**的儿子有没有这个字符?,如果没有,再找它爸爸的fail的fail的儿子有没有这个字符,(这句话可能有点绕,结合kmp的意思理解一下)直至找到匹配的节点或者根节点。

    ACTree

    我们习惯上用用队列作为工具来计算fail指针

        void bulid_fail()
        {
            int now=0;
            int to;
            /*
            保证队列里的fail指针全部OK
            从队列中取一个 把其下面的fail指针OK 并且加入队列
            */
            queue<int> mmp;
            for(int i=0;i<26;++i)//将根节点的所有儿子加入队列
            {
                if(node[0].next[i]!=-1)
                    mmp.push(node[0].next[i]);
            }
            while(!mmp.empty())//
            {
                now=mmp.front();
                mmp.pop();
                for(int i=0;i<26;++i)//将此节点的儿子的 fail指针计算出来并加入队列
                {
                    if(node[now].next[i]!=-1)
                    {
                        mmp.push(node[now].next[i]);
                        /*   计算now的 第i个儿子的fail指针   */
                        to=node[now].fail;
                        while(to>0&&node[to].next[i]==-1)
                            to=node[to].fail;
                        /* 直至根节点 或者fail的父节点*/
                        if(node[to].next[i]!=-1)//否则没有最大匹配  即为空
                            node[node[now].next[i]].fail=node[to].next[i];
                    }
                }
            }
    
        }
    

    fail指针建好之后 就是文本串在AC自动机上的匹配了

     int Find_words(char *tx)
        {
            int ans=0;
            int now=0;//当前最大匹配下标
            int to,i;
            while(*tx)
            {
                i=hash_letter(*tx);
                //没有下面的匹配,则找跟此字符的最大匹配
                while(now>0&&node[now].next[i]==-1)
                        now=node[now].fail;
                if(node[now].next[i]!=-1)
                    now=node[now].next[i];
                /* 开始计算以now为后缀的单词出现数*/
                to=now;
                while(to&&node[to].cnt!=-1)/*精髓之处,匹配过后设为-1代表该后缀对应所有的字符串都匹配过了,避免后来重复匹配已经匹配过的单词*/
                {
                    ans+=node[to].cnt;
                    node[to].cnt=-1;//标记该单词已经加过了  就算这个单词没出现 标记为-1代表这个字符串的后缀节点全扫描过了
                    to=node[to].fail;
                }
                ++tx;
            }
            return ans;
        }
    

    AC自动机模板题

    hdu2222http://acm.hdu.edu.cn/showproblem.php?pid=2222

    模板代码

    #include<stdio.h>
    #include<string.h>
    #include<algorithm>
    #include<math.h>
    #include<queue>
    using namespace std;
    const int maxn=5e5+7;
    struct AcTireNode
    {
        int next[26];
        int fail,cnt;//要保证fail指针不能为自己
        void clear()
        {
            memset(next,-1,sizeof(next));
            fail=cnt=0;//初始化
        }
    };
    class AcTire{
        /*
    AC 自动机
    1.输入模式串 构建字典树
    2.构建fail指针
    3.匹配文本串
    4.初始化树
    */
        public:
            AcTireNode *node;
            int top;//使用了多少个节点
        AcTire()
        {
            top=0;
            node=new AcTireNode[maxn];
            node[0].clear();
        }
        inline int hash_letter(char c)
        {
            return c-'a';
        }
        void insert(char *p)
        {
            int now=0;
            while(*p)
            {
                if(node[now].next[hash_letter(*p)]==-1)
                {
                    node[now].next[hash_letter(*p)]=++top;
                    node[top].clear();//初始化该节点的信息
                }
                    
                now=node[now].next[hash_letter(*p)];
                ++p;
            }
            node[now].cnt++;
        }
        void bulid_fail()
        {
            int now=0;
            int to;
            /*
            保证队列里的fail指针全部OK
            从队列中取一个 把其下面的fail指针OK 并且加入队列
            */
            queue<int> mmp;
            for(int i=0;i<26;++i)//将根节点的所有儿子加入队列
            {
                if(node[0].next[i]!=-1)
                    mmp.push(node[0].next[i]);
            }
            while(!mmp.empty())//
            {
                now=mmp.front();
                mmp.pop();
                for(int i=0;i<26;++i)//将此节点的儿子的 fail指针计算出来并加入队列
                {
                    if(node[now].next[i]!=-1)
                    {
                        mmp.push(node[now].next[i]);
                        /*   计算now的 第i个儿子的fail指针   */
                        to=node[now].fail;
                        while(to>0&&node[to].next[i]==-1)
                            to=node[to].fail;
                        /* 直至根节点 或者fail的父节点*/
                        if(node[to].next[i]!=-1)//否则没有最大匹配  即为空
                            node[node[now].next[i]].fail=node[to].next[i];
                    }
                }
            }
    
        }
        int Find_words(char *tx)
        {
            int ans=0;
            int now=0;//当前最大匹配下标
            int to,i;
            while(*tx)
            {
                i=hash_letter(*tx);
                //没有下面的匹配,则找跟此字符的最大匹配
                while(now>0&&node[now].next[i]==-1)
                        now=node[now].fail;
                if(node[now].next[i]!=-1)
                    now=node[now].next[i];
                /* 开始计算以now为后缀的单词出现数*/
                to=now;
                while(to&&node[to].cnt!=-1)/*精髓之处,匹配过后设为-1代表该后缀对应所有的字符串都匹配过了,避免后来重复匹配已经匹配过的单词*/
                {
                    ans+=node[to].cnt;
                    node[to].cnt=-1;//标记该单词已经加过了  就算这个单词没出现 标记为-1代表这个字符串的后缀节点全扫描过了
                    to=node[to].fail;
                }
                ++tx;
            }
            return ans;
        }
        void clear()
        {
            node[0].clear();
            top=0;
        }
        ~AcTire()
        {
            delete []node;
        }
    
    };
    char tx[1000007],words[55];
    int main()
    {
        AcTire dch;
        int t;
        int n;
    
        scanf("%d",&t);
        while(t--)
        {
            dch.clear();
            scanf("%d",&n);
            for(int i=0;i<n;++i)
            {
                scanf("%s",words);
                dch.insert(words);
            }
            dch.bulid_fail();
            scanf("%s",tx);
            printf("%d
    ",dch.Find_words(tx));
        }
    }
    
  • 相关阅读:
    STL map与Boost unordered_map
    static constructors in C++? need to initialize private static objects
    谈谈C++继承中的重载,覆盖和隐藏
    C++继承:公有,私有,保护
    C++ using关键字作用总结
    ++iter的效率比iter++的效率高
    C++中dynamic_cast,static_cast,const_cast,reinterpret_cast
    【转】WinForm中添加闪屏窗口的两种方法
    【转】一个不错的Matlab的gui界面设计实例 (20081003 15:47:30)matlab gui 界面 校园 分类:Matlab实例
    制作一个WinForm的闪屏
  • 原文地址:https://www.cnblogs.com/dchnzlh/p/10427290.html
Copyright © 2020-2023  润新知