zoukankan      html  css  js  c++  java
  • Trie图(DFA),AC自动机

    Trie图

     先看一个问题:给一个很长很长的母串 长度为n,然后给m个小的模式串。求这m个模式串里边有多少个是母串的字串。

    最先想到的是暴力O(n*m*len(m)) len(m)表示这m个模式串的平均长度。。。

    显然时间复杂度会很高。。。

    再改进一些,用kmp让每一模式串与母串进行匹配呢?时间复杂度为O((n + len(m))*m),还算可以。

    可是还有没有更快的算法呢?

    编译原理里边有一个很著名的思想:自动机。

    这里就要用到确定性有限状态自动机(DFA)。可以对这m个模式串建立一个DFA,然后让母串在DFA上跑,遇到某个模式串的终结节点则表示这个模式串在母串上。

    就像这个图,母串“nano”在上边跑就能到达终止节点。

    上边说的是自动机的概念。。。还有一个要用到的是trie树,这个不解释了,网上资料一大堆。

    这里步入正题:Trie图

    trie图是一种DFA,可以由trie树为基础构造出来,
    对于插入的每个模式串,其插入过程中使用的最后一个节点都作为DFA的一个终止节点。
    如果要求一个母串包含哪些模式串,以用母串作为DFA的输入,在DFA 上行走,走到终止节点,就意味着匹配了相应的模式串。

    ps: AC自动机是Trie的一种实现,也就是说AC自动机是构造Trie图的DFA的一种方法。还有别的构造DFA的方法... 

    怎么建Trie图?

    可以回想一下,在kmp算法中是如何避免母串在匹配过程种指针回溯的?也就是说指针做不必要的前移,浪费时间。

    同样的,在trie图中也定义这样一个概念:前缀指针。

    这个前缀指针,从根节点沿边到节点p我们可以得到一个字符串S,节点p的前缀指针定义为:指向树中出现过的S的最长的后缀。

    构造前缀指针的步骤为:根据深度一一求出每一个节点的前缀指针。对于当前节点,设他的父节点与他的边上的字符为Ch,如果他的父节点的前缀指针所指向的节点的儿子中,有通过Ch字符指向的儿子,那么当前节点的前缀指针指向该儿子节点,否则通过当前节点的父节点的前缀指针所指向点的前缀指针,继续向上查找,直到到达根节点为止。

    上图构造出所有节点的前缀指针。

    相信原来的问题到这里基本已经解决了。可以再考虑一下它的时间复杂度,设M个串的总长度为LEN

    所以算法总的时间复杂度为O(LEN + n)。比较好的效率。

    模板,HDU 2222:

    /*
    
    个人感觉这样写更清晰一点。(动态分配内存)
    
    */
    class Node {
    public:
        Node* fail;
        Node* next[26];
        int cnt;
        Node() {
            CL(next, 0);
            fail = NULL;
            cnt = 0;
        }
    };
    
    //Node* q[10000000];
    
    class AC_automaton : public Node{
    public:
        Node *root;
        int head, tail;
    
        void init() {
            root = new Node();
            head = tail = 0;
        }
    
        void insert(char* st) {
            Node* p = root;
            while(*st) {
                if(p->next[*st-'a'] == NULL) {
                    p->next[*st-'a'] = new Node();
                }
                p = p->next[*st-'a'];
                st++;
            }
            p->cnt++;
        }
    
        void build() {
            root->fail = NULL;
            deque<Node* > q;
            q.push_back(root);
    
            while(!q.empty()) {
                Node* tmp = q.front();
                Node* p = NULL;
                q.pop_front();
                for(int i = 0; i < 26; ++i) {
                    if(tmp->next[i] != NULL) {
                        if(tmp == root) tmp->next[i]->fail = root;
                        else {
                            p = tmp->fail;
                            while(p != NULL) {
                                if(p->next[i] != NULL) {
                                    tmp->next[i]->fail = p->next[i];
                                    break;
                                }
                                p = p->fail;
                            }
                            if(p == NULL)   tmp->next[i]->fail = root;
                        }
                        q.push_back(tmp->next[i]);
                    }
                }
            }
        }
    
        int search(char* st) {
            int cnt = 0, t;
            Node* p = root;
            while(*st) {
                t = *st - 'a';
                while(p->next[t] == NULL && p != root) {
                    p = p->fail;
                }
                p = p->next[t];
                if(p == NULL)   p = root;
    
                Node* tmp = p;
                while(tmp != root && tmp->cnt != -1) {
                    cnt += tmp->cnt;
                    tmp->cnt = -1;
                    tmp = tmp->fail;
                }
                st++;
            }
            return cnt;
        }
    }AC;

    POJ 1204:

    View Code
    #include <iostream>
    #include <cstdio>
    #include <cmath>
    #include <vector>
    #include <cstring>
    #include <algorithm>
    #include <string>
    #include <set>
    #include <ctime>
    #include <queue>
    #include <map>
    #include <sstream>
    
    #define CL(arr, val)    memset(arr, val, sizeof(arr))
    #define REP(i, n)       for((i) = 0; (i) < (n); ++(i))
    #define FOR(i, l, h)    for((i) = (l); (i) <= (h); ++(i))
    #define FORD(i, h, l)   for((i) = (h); (i) >= (l); --(i))
    #define L(x)    (x) << 1
    #define R(x)    (x) << 1 | 1
    #define MID(l, r)   (l + r) >> 1
    #define Min(x, y)   x < y ? x : y
    #define Max(x, y)   x < y ? y : x
    #define E(x)    (1 << (x))
    
    const int eps = 1e-6;
    const int inf = ~0u>>2;
    typedef long long LL;
    
    using namespace std;
    
    const int N = 1024;
    const int LET = 26;
    int nNodesCount = 0;
    
    struct CNode {
        CNode * ch[LET];
        CNode * pPre;
        vector<int> bstopNode;    //同一个节点可能会是多个串的终止节点。
        int num;
        CNode() {
            CL(ch, 0);
            bstopNode.clear();
            pPre = NULL;
        }
    };
    
    CNode T[100000];
    char mp[N][N];
    int r, c, m;
    bool vis[N] = {false};
    
    
    int dir[8][2] = {{-1, 0}, {-1, 1}, {0, 1}, {1, 1}, {1, 0}, {1, -1}, {0, -1}, {-1, -1}};
    
    void insert(CNode* p, char* s, int x) {
        int i, l = strlen(s);
        for(i = l - 1; i >= 0; --i) {
            if(p->ch[s[i]-'A'] == NULL) {
                p->ch[s[i]-'A'] = T + nNodesCount++;
            }
            p = p->ch[s[i] - 'A'];
        }
        p->bstopNode.push_back(x);
    }
    
    void buildDFA() {
        int i;
        for(i = 0; i < LET; ++i) {
            T[0].ch[i] = T + 1;
        }
        T[0].pPre = NULL;
        T[1].pPre = T;
    
        deque<CNode *> q;    //....
        q.push_back(T + 1);
    
        while(!q.empty()) {
            CNode * proot = q.front();
            q.pop_front();
            for(i = 0; i < LET; ++i) {
                CNode* p = proot->ch[i];
    
                if(p) {
                    CNode* father = proot->pPre;
                    while(father) {
                        if(father->ch[i]) {
                            p->pPre = father->ch[i];
    
                            if(p->pPre->bstopNode.size() != 0) {
                                vector<int>::iterator it;
                                for(it = p->pPre->bstopNode.begin(); it != p->pPre->bstopNode.end(); ++it)    //合并终止节点
                                    p->bstopNode.push_back(*it);
                            }
    
                            break;
                        } else
                            father = father->pPre;
                    }
                    q.push_back(p);
                }
            }
        }
    }
    
    bool inmap(int x, int y) {
        if(x < 0 || x >= r || y < 0 || y >= c)  return false;
        return true;
    }
    
    struct node {
        int x, y;
        char c;
        node() {}
        node(int a, int b, char d) : x(a), y(b), c(d) {}
    } ans[10000];
    
    bool search(int sx, int sy, int d) {
        CNode* p = T + 1;
        int x, y;
        for(x = sx, y = sy; inmap(x, y); x += dir[d][0], y += dir[d][1]) {
            while(true) {
                if(p->ch[mp[x][y] - 'A']) {
                    p = p->ch[mp[x][y] - 'A'];
                    if(p->bstopNode.size() != 0) {
                        //printf("%d %d %d\n", x, y, p->num);
                        vector<int>::iterator it;
                        for(it = p->bstopNode.begin(); it != p->bstopNode.end(); ++it)
                            if(!vis[*it]) {    //记录多个终止节点
                                ans[*it] = node(x, y, (d + 4)%8 + 'A');
                                vis[*it] = true;
                            }
                        //return true;
                    }
                    break;
                } else  p = p->pPre;
            }
        }
        return false;
    }
    
    void solve() {
        buildDFA();
        int i;    //枚举8整个矩阵的一圈,作为起点走八个方向。
        for(i = 0; i < r; ++i) {
            search(i, 0, 2);
            search(i, 0, 1);
            search(i, 0, 3);
            search(i, c - 1, 6);
            search(i, c - 1, 5);
            search(i, c - 1, 7);
        }
        for(i = 0; i < c; ++i) {
            search(0, i, 4);
            search(0, i, 5);
            search(0, i, 3);
            search(r - 1, i, 0);
            search(r - 1, i, 1);
            search(r - 1, i, 7);
        }
    }
    
    int main() {
        //freopen("data.in", "r", stdin);
    
        int i;
        scanf("%d%d%d", &r, &c, &m);
        for(i = 0; i < r; ++i) {
            scanf("%s", mp[i]);
        }
        char st[N];
        nNodesCount = 2;
        for(i = 0; i < m; ++i) {
            scanf("%s", st);
            insert(T + 1, st, i);
        }
        solve();
        for(i = 0; i < m; ++i) {
            printf("%d %d %c\n", ans[i].x, ans[i].y, ans[i].c);
        }
        return 0;
    }
  • 相关阅读:
    template(2.2)
    Filter过滤链条
    The 3n + 1 problem
    Struts2.3+Spring4.0
    康托展开
    templates(2.1)
    templates(1.2)
    templates(1.1)
    我和你
    Android 的上下文菜单: Context Menu
  • 原文地址:https://www.cnblogs.com/vongang/p/2606494.html
Copyright © 2011-2022 走看看