AC自动机
全称:Aho-Corasick automation
结构:Trie树 + 失败指针fail
应用:多模式串匹配
优点:结合Trie树的查询特点和KMP的失配转移,加快多模式串匹配速度
AC自动机特点
Trie树的基础上扩展,插入查询与Trie树类似
每个节点建立了失配指针fail,与KMP的next类似
匹配过程
当前匹配成功时,与Trie树的查询类似
当前匹配失败时,通过fail实现类似与KMP的next效果,转移到fail指向
根节点root的fail指针,指向自己
其他节点fail指针,指向后缀子串的节点
若不存在相同后缀的节点,则fail指向root
失败指针的含义
一个模式串匹配失败的时候
跳到它的失败指针上继续匹配
满足的性质:
它指向的一定是某个串的前缀
这个前缀是当前节点串的后缀
且一定是最长后缀
AC自动机的实现
定义含有fail指针的Trie树
指向子节点的指针
当前节点的值
匹配失败时,指向下一个位置fail指针
struct Trie { int root, L; // root - 根节点下标, L – 节点数目 int ch[MAX][26], fail[MAX], v[MAX]; // fail[i] – 失败指针 int newnode(); // 新节点初始化,返回下标 void init(); // 字典树初始化 void Insert(char str[]); // 插入字符串str void Build(); // 构造fail指针 int query(); // 匹配多模式串,返回成功匹配数目 void debug(); // 调试函数 }ac; // 设置变量
初始化
int Trie::newnode() { v[L] = 0; memset(ch[L], -1, sizeof(ch[L])); return L++; } void Trie::init() { L = 0; root = newnode(); }
void Trie::Insert(char str[]) { int now = root; for(int i = 0; str[i]; i++) { if(ch[now][str[i]-'a'] == -1) ch[now][str[i]-'a'] = newnode(); now = ch[now][str[i]-'a']; } v[now] ++; }
void Trie::Build() { int now; queue<int> q; fail[root] = root; for(int i = 0; i<26; i++) { if(ch[root][i] == -1) { ch[root][i] = root;} else { fail[ch[root][i]] = root; q.push(ch[root][i]); } } while(!q.empty()) { now = q.front(); q.pop(); for(int i = 0; i<26; i++) { if(ch[now][i] == -1) { ch[now][i] = ch[fail[now]][i]; } else { fail[ch[now][i]] = ch[fail[now]][i]; q.push(ch[now][i]); } } } }
int Trie::query() { int res = 0; int now = root, tmp; for(int i = 0; s[i]; i++) { tmp = now = ch[now][s[i]-'a']; // 沿着fail向上遍历后缀所有节点 while(tmp != root) { res += v[tmp]; v[tmp] = 0; tmp = fail[tmp]; } } return res; }