Aho-Corasick自动机
算法:
<功能>
AC自动机用于解决文本一个而模板有多个的问题。
AC自动机可以成功将多模板匹配,匹配意味着算法可以找到每一个模板在文本中出现的位置。
<解释>
KMP中对模板构造失配边,多模板每条模板独立构造失配边太过麻烦。
算法利用Trie+KMP中的失配边。insert(模板) 构造Trie+ getFail添加失配边->AC自动机的状态转移图。
匹配文本串text时只需要调用find,find依次匹配text中的每一个字符失败则沿着失配边走,在匹配路径上如果遇到单词结点(val != 0 )即相当于匹配成功。
但需要注意到:可能有作为当前后缀的单词已经成功匹配,所以需要加入后缀链接last[] 在每一个结点都要处理这种情况。last递推得到。
作者所给模板如下:
1 struct AhoCorasickAutomata { 2 int ch[MAXNODE][SIGMA_SIZE]; 3 int f[MAXNODE]; // fail函数 4 int val[MAXNODE]; // 每个字符串的结尾结点都有一个非0的val 5 int last[MAXNODE]; // 输出链表的下一个结点 6 int cnt[MAXS]; 7 int sz; 8 9 void init() { 10 sz = 1; 11 memset(ch[0], 0, sizeof(ch[0])); 12 memset(cnt, 0, sizeof(cnt)); 13 ms.clear(); 14 } 15 16 // 字符c的编号 17 int idx(char c) { 18 return c-'a'; 19 } 20 21 // 插入字符串 v必须非0 22 void insert(char *s, int v) { 23 int u = 0, n = strlen(s); 24 for(int i = 0; i < n; i++) { 25 int c = idx(s[i]); 26 if(!ch[u][c]) { 27 memset(ch[sz], 0, sizeof(ch[sz])); 28 val[sz] = 0; 29 ch[u][c] = sz++; 30 } 31 u = ch[u][c]; 32 } 33 val[u] = v; 34 ms[string(s)] = v; 35 } 36 37 // 递归打印以结点j结尾的所有字符串 38 void print(int j) { 39 if(j) { 40 cnt[val[j]]++; 41 print(last[j]); 42 } 43 } 44 45 // 在T中找模板 46 int find(char* T) { 47 int n = strlen(T); 48 int j = 0; // 当前结点编号 初始为根结点 49 for(int i = 0; i < n; i++) { // 文本串当前指针 50 int c = idx(T[i]); 51 while(j && !ch[j][c]) j = f[j]; // 顺着细边走 直到可以匹配 52 j = ch[j][c]; 53 if(val[j]) print(j); 54 else if(last[j]) print(last[j]); // 找到了 55 } 56 } 57 58 // 计算fail函数 59 void getFail() { 60 queue<int> q; 61 f[0] = 0; 62 // 初始化队列 63 for(int c = 0; c < SIGMA_SIZE; c++) { 64 int u = ch[0][c]; 65 if(u) { f[u] = 0; q.push(u); last[u] = 0; } 66 } 67 // 按BFS顺序计算fail 68 while(!q.empty()) { 69 int r = q.front(); q.pop(); 70 for(int c = 0; c < SIGMA_SIZE; c++) { 71 int u = ch[r][c]; 72 if(!u) continue; 73 q.push(u); 74 int v = f[r]; 75 while(v && !ch[v][c]) v = f[v]; 76 f[u] = ch[v][c]; 77 last[u] = val[f[u]] ? f[u] : last[f[u]]; 78 } 79 } 80 } 81 82 };