AC自动机
来源:互联网 发布:智商高 成功 知乎 编辑:程序博客网 时间:2024/06/04 08:55
在字典树中加入失配链(也可以叫做失败指针)就可以构成AC自动机,可以用来解决多模式匹配的问题。给定多个模式串P0、P1、……,给定目标串T,问T中包含了哪些P……等等问题。
在AC自动机中,每一个节点都有一个失败指针指向自动机中的另外一个节点;除了根节点,根节点的失败指针指向NULL。假设节点A的失败指针指向节点B,说明节点A的向上的字符串与从根到B的字符串匹配,而且匹配长度是最长的。
如下的AC自动机,红色箭头代表失败指针。sh节点的失败指针指向h,因为sh的后缀与h是匹配的;she的失败指针指向he,因为she的后缀与he匹配;sheb的失败指针指向eb……当然,she的失败指针不指向e,是因为he匹配的更长。与此同时,所有其他的非根节点都有失败指针,只不过都指向根节点,就没有画出来。很显然,失败指针都是指向更靠上的节点。
AC自动机的使用过程基本分为3步:首先建字典树,其次在字典树上建立失败指针,最后是查询。字典树的建立过程比较简单, 可以参考这里。失败指针的建立是一个BFS的过程,利用已求出失败指针的节点去解未知的节点。
考虑某个节点v,它的失败指针应该指向哪里?假设v的父节点为f,v在f的排行是sn,f节点的失败指针指向f2,则如果f2也有sn儿子v2,则v的失败指针就应该指向v2。如果f2没有sn儿子,就应该去找f2的失败指针指向的节点f3,如果f3有sn儿子v3,则v的失败指针就应该指向v3。否则就应该再沿着失败指针向上走。
- 首先,所有一级子节点的失败指针都指向根,且入队
- 当队列不为空
- 取出头节点u,
- 对u的每个儿子v
1.沿着u的失败指针一直走,直到找到相同排序的儿子或者到达了根
2.如果没有相同排序的儿子,则v的失败指针指向根
3.否则指向相同排序的那个节点
4.将v入队
AC自动机的查询与字典树类似,给定目标串T和AC自动机,对T中的每个字母t,如果当前节点有t儿子,则前往t儿子节点;否则沿着失配链一直走,直到某个节点有t儿子或者到达了根节点。所以失败指针就是在匹配失败的时候起作用。
AC自动机查询的另一要点是:对每个节点,需要查询整个失配链!如上图的AC自动机,其字典是{shea,sheb,h,he,eb},假设给定字符串为sheb,问该字符串包含了字典中的几个单词?如果只沿着路径往下,最后停留在叶子节点,答案就是1;但实际上答案是4,只要统计了失配链上的所有节点,就能得到正确答案。考虑到整个失配链,显然不会是所有节点都是单词的结尾,所以还可以建立专门的指针指向这些节点,用以加快失配链的搜索速度。
AC自动机实际上就是在字典树上做KMP,反过来可以把KMP看做是单单词字典树的AC自动机。考虑到字符串aaaa,其特征向量是(0123),其AC自动机如下,可以看到失败指针恰好可以对应特征向量。
hdu2222是基本的AC自动机题目。给定一系列的关键词,问T中包含多少个关键词。这道题题意稍微有点模糊。第一,关键词集合中可能包含一模一样的单词,如果K在关键词集合中出现了n次,则T中出现一次K的时候就必须认为T包含了n个关键词;第二,如果关键词K只在集合中出现了一次,而是在T中重复出现了多次,则只能说T中包含了一个关键词。由于给定一个字典,只需查询一个T,所以在查询的时候修改节点标记,可以一下解决这两个问题。
#include <cstdio>#include <cstring>#include <queue>using namespace std;#define SIZE 1000001/*trie树,node[0]是root*/struct node_t{ node_t* child[26]; node_t* failer; int cnt;//表示该单词在字典中出现的次数}Node[10000*51];int toUsed = 1;/*建立trie树*/void insert(char const word[]){ node_t* loc = Node; for(int i=0;word[i];++i){ int sn = word[i] - 'a' ; if ( !loc->child[sn] ){ memset(Node+toUsed,0,sizeof(node_t)); loc->child[sn] = Node + toUsed ++; } loc = loc->child[sn]; } ++loc->cnt;}/*建立失败指针*/void buildAC(){ Node[0].failer = NULL;/*root的failer为空*/ queue<node_t*> q; for(int i=0;i<26;++i){//一级子节点的失败指针指向根 node_t* p = Node[0].child[i]; if ( p ){ p->failer = Node; q.push(p); } } while( !q.empty() ){ node_t* father = q.front(); /*取出1个节点*/ q.pop(); for(int i=0;i<26;++i){ node_t* p = father->child[i]; if ( p ){ node_t* v = father->failer; while ( v && !v->child[i] ) v = v->failer; /*如果不匹配反复寻找failer,v为空说明已经到根节点*/ /*判断v为空一定要放在前面*/ if ( !v ) p->failer = Node;/*如果v为空,则failer指向root*/ else p->failer = v->child[i]; q.push(p); } } }}/*搜索,返回匹配的单词数量*/int search(char const word[]){ int ans = 0; node_t* loc = Node; for(int i=0;word[i];++i){ int sn = word[i] - 'a'; while( loc && !loc->child[sn] ) /*沿着分支或者失败指针一直找,直到找到或者到root*/ loc = loc->failer; loc = loc ? loc->child[sn] : Node; /*定位到新的节点*/ node_t* p = loc; /*将该节点所在的失配链的所有cnt加上*/ while( p != Node && p->cnt >= 0 ){ ans += p->cnt; p->cnt = -1; /*表明该失配链已经匹配过了,以后不必再考虑*/ p = p->failer; } } return ans;}char T[SIZE],Word[55];int main(){ int nofkase; scanf("%d",&nofkase); while(nofkase--){ toUsed = 1; memset(Node,0,sizeof(node_t)); int n; scanf("%d",&n); for(int i=0;i<n;++i){ scanf("%s",Word); insert(Word); } buildAC(); scanf("%s",T); printf("%d\n",search(T)); } return 0;}
- AC自动机...
- AC自动机
- AC 自动机
- AC自动机
- AC自动机
- ac自动机
- ac自动机
- AC自动机
- AC自动机
- AC自动机
- AC自动机
- AC自动机
- AC 自动机
- ac自动机
- AC自动机
- AC自动机
- AC自动机
- AC自动机
- Git和GitHub在线学习资源整理
- 分享一百多套开发视频教程的下载地址
- add-apt-repository
- 【SICP练习】148 练习4.4
- dsa算法(20)
- AC自动机
- 二叉树后序遍历的非递归算法
- 在Linux上安装SAP Netweaver/ERP
- Xms Xmx PermSize MaxPermSize 区别
- 一个人的成就在于晚上做什么
- android开发——activity的使用
- CSS控制LI行字符溢出用省略号代替
- Anaconda安装以及ipython notebook简介
- ios-day17-06(手势识别器之“长按”和“滑动”)