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,分析过程未知。

  • 相关阅读:
    (转载)C++ string中find() ,rfind() 等函数 用法总结及示例
    UVA 230 Borrowers (STL 行读入的处理 重载小于号)
    UVA 12100 打印队列(STL deque)
    uva 12096 The SetStack Computer(STL set的各种库函数 交集 并集 插入迭代器)
    uva 1592 Database (STL)
    HDU 1087 Super Jumping! Jumping! Jumping!
    hdu 1176 免费馅饼
    HDU 1003 Max Sum
    转战HDU
    hust 1227 Join Together
  • 原文地址:https://www.cnblogs.com/ljt12138/p/6684383.html
Copyright © 2011-2022 走看看