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:

#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; }