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;
    }
  • 相关阅读:
    Linq聚合操作之Aggregate,Count,Sum,Distinct源码分析
    Linq分区操作之Skip,SkipWhile,Take,TakeWhile源码分析
    Linq生成操作之DefautIfEmpty,Empty,Range,Repeat源码分析
    Linq基础操作之Select,Where,OrderBy,ThenBy源码分析
    PAT 1152 Google Recruitment
    PAT 1092 To Buy or Not to Buy
    PAT 1081 Rational Sum
    PAT 1084 Broken Keyboard
    PAT 1077 Kuchiguse
    PAT 1073 Scientific Notation
  • 原文地址:https://www.cnblogs.com/vongang/p/2606494.html
Copyright © 2011-2022 走看看