zoukankan      html  css  js  c++  java
  • AC自动机的另类匹配算法

    思路

    蒟蒻觉得ac自动机的匹配过程太繁琐,于是决定手制一个好理解的匹配算法。经过一天的艰苦奋斗,终于用我的递归算法用900s的擦边成绩A掉了hdu2222的模板题。(O-O)好开心。。
    简要说一下思路,就是将字符串直接在树上匹配,如果无法匹配或字符串已经结束,则用fail指针跳转。不难发现,如果对于所有模式串都互不为子串,那么这样的算法已经可以交代了。证明过程大致如下:

    对于当前串P匹配上了字符串S[0],证明P->fail不需要匹配S。

    证明:设Q=PS[0],S=S[1],T=Pfail,如果T没有孩子S’,显然不存在匹配;如果T有孩子S’,则一定可以归纳为对于Q匹配S’的情况,最终要么如上情况,要么匹配失败而跳转,从而匹配到。

    然而还有一个特殊情况,就是两个子串存在前缀包含关系(如abc和ab),需要一个特判,即当节点为finish但后面还有节点时,在这次把finish和通过fail与这个finish链接的所有finish清算一下。


    5.1 补充:
    由于递归几乎是线性的,简单的改为非递归,差不多600sAC,见下面代码的match2,完全由递归版改来。

    匹配 {
        如果字符串终止,找到所有fail可达且已经finish的节点并计算返回
        count = 0
        如果当前节点finish,count加上finish
        如果存在孩子str[0],沿着树向下匹配,并加上count和所有fail可达且finish的节点并返回
        如果节点为root,匹配str+1
        // 匹配形如 she --> sheee,否则会死循环
        否则,通过fail跳转计算
    }
        int match(Node _nd, const char* _str)
        {
            if (*_str == '') {
                if (_nd->finish) {
                    int t = _nd->finish; _nd->finish = 0;
                    return t+match(_nd->fail, _str);
                }
                if (_nd == root)
                    return 0;
                return match(_nd->fail, _str);
            }
            int count = 0;
            if (_nd->finish)
                count = _nd->finish;
            _nd->finish = 0;
            if (_nd->children[*_str-'a'])
                return match(_nd->children[*_str-'a'], _str+1)+count+match(_nd->fail, emp);
            if (_nd == root)
                return match(_nd, _str+1);
            return match(_nd->fail, _str)+count;
        }

    代码

    多说无益,上我的擦边AC代码:
    重点

    1. 有重复key,算多个
    2. 同一个文章里只能匹配一次某个key
    #include <iostream>
    #include <cstdio>
    #include <queue>
    #include <stack>
    using namespace std;
    
    struct AcTrieNode {
        typedef AcTrieNode *Node;
        int finish = 0;
        Node children[26];
        Node fail;
        //Node pre;
        //char _this;
        AcTrieNode():finish(0),fail(0)
        {
            for (int i=0; i<26; i++)
                children[i] = 0;
        }
    };
    
    class AcAutoMachine {
    private:
        typedef AcTrieNode *Node;
        Node root;
        char emp[1];
        void push(Node &nd, const char * _str)
        {
            if (!nd)
                nd = new AcTrieNode;
            if (*_str=='') {
                nd->finish++;
                return;
            }
            push(nd->children[(*_str)-'a'], _str+1);
        }
        void dfs(Node tree)
        {
            if (tree) {
                for (int i=0; i<26; i++)
                    if (tree->children[i]) {
                        cout << (char)(i+'a');
                    dfs(tree->children[i]);
                }
    
            }
        }
        int match(Node _nd, const char* _str)
        {
            if (*_str == '') {
                if (_nd->finish) {
                    int t = _nd->finish; _nd->finish = 0;
                    return t+match(_nd->fail, _str);
                }
                if (_nd == root)
                    return 0;
                return match(_nd->fail, _str);
            }
            int count = 0;
            if (_nd->finish)
                count = _nd->finish;
            _nd->finish = 0;
            if (_nd->children[*_str-'a'])
                return match(_nd->children[*_str-'a'], _str+1)+count+match(_nd->fail, emp);
            // 不可能再算到的
            if (_nd == root)
                return match(_nd, _str+1);
            return match(_nd->fail, _str)+count;
        }
    
        int match2(const char* _str)
        {
            int ans = 0;
            Node _nd = root;
            while (1) {
                //cout << _str << endl;
                if (*_str == '') {
                    if (_nd->finish) {*/
                        ans += _nd->finish;
                        _nd->finish = 0;
                        // 2
                    }
                    if (_nd == root)
                        break;
                    _nd = _nd->fail;
                    continue;
                }
                int count = 0;
                if (_nd->finish)
                    count = _nd->finish;
                _nd->finish = 0;
                // 2
                if (_nd->children[*_str-'a']){
                    ans += count;
                    Node nd = _nd->fail;
                    while (1) {
                        if (nd->finish) {
                            ans += nd->finish;
                            nd->finish = 0;
                        }
                        if (nd == root)
                            break;
                        nd = nd->fail;
                    }
                    _nd = _nd->children[*_str-'a'];
                    ++_str;
                    continue;
                }
    
                // 不可能再算到的
                if (_nd == root) {
                    _str++;
                    continue;
                }
                ans += count;
                _nd = _nd->fail;
            }
            return ans;
        }
    
    public:
        AcAutoMachine()
        {
            root = new AcTrieNode;
            emp[0] = '';
        }
        void push(const char * _str)
        {
            push(root, _str);
        }
        void init()
        {
            queue<Node> q;
            q.push(root);
            while (!q.empty()) {
                Node temp = q.front(), prt;
                q.pop();
                for (int i=0; i<26; i++) {
                    if (temp->children[i]) {
                        q.push(temp->children[i]);
                        prt = temp->fail;
                        while (prt){
                            if (prt->children[i]) {
                                prt = prt->children[i];
                                break;
                            }
                            prt = prt->fail;
                        }
                        if (!prt) prt = root;
                        temp->children[i]->fail = prt;
                    }
                }
            }
            root->fail = root;
        }
        void dfs()
        {
            dfs(root);
        }
        int match(const char* _str)
        {
            //return match(root, _str);
            return match2(_str);
        }
    };
    
    char a[55], b[1000005];
    int main()
    {
        //freopen("data.txt", "r", stdin);
        int cas;
        cin >> cas;
        while (cas--) {
        AcAutoMachine ac;
        int n;
        scanf("%d
    ",&n);
        while (n--) {
            gets(a);
            ac.push(a);
        }
        ac.init();
        gets(b);
        //ac.dfs();
        int ans = 0;
        ans = ac.match(b);
        cout << ans << endl;
        }
        return 0;
    }

    闲话

    AC automachine is not ACcepted automachine (O-O)。代码改了一天,递归版程序由40行降到了20行,思路终于明晰了。。

    本来还想分析一下效率,试着用聚合分析,结果分析不通。。看来聚合分析这种东西看着简单做着难,毕竟还是蒟蒻。不过既然(循环版)能600msAC,看来复杂度应该没有问题,只是常数稍大。

    the algorithm is linear in the length of the strings plus the length of the searched text plus the number of output matches
    wiki,分析过程未知。

  • 相关阅读:
    ubuntu 修改mysql 5.7数据库密码
    maven 配置
    数据仓库的命名规范
    mysql 之 在查询字段中得出分钟数
    mysql 之 timestampdiff() 函数 ,得到间隔分钟数
    linux 服务器上下载文件到本地
    mysql 之 时间格式 今年的第一天,去年的第一天
    mysql 之 str_to_date ()函数 和date_format()函数
    网络不可用时~更改DNS并刷新
    mysql之 round()函数 , concat()函数
  • 原文地址:https://www.cnblogs.com/ljt12138/p/6684383.html
Copyright © 2011-2022 走看看