浅谈AC自动机
来源:互联网 发布:网页游戏优化器 编辑:程序博客网 时间:2024/06/05 23:42
以前我觉得AC自动机很高大尚,然而并不是这样的,其实很简单。
我们首先来复习一下
KMP
一种由Knuth(D.E.Knuth)、Morris(J.H.Morris)和Pratt(V.R.Pratt)三人设计的线性时间字符串匹配算法。这个算法不用计算变迁函数δ,匹配时间为Θ(n),只用到辅助函数π[1,m],它是在Θ(m)时间内,根据模式预先计算出来的。数组π使得我们可以按需要,“现场”有效的计算(在平摊意义上来说)变迁函数δ。粗略地说,对任意状态q=0,1,…,m和任意字符a∈Σ,π[q]的值包含了与a无关但在计算δ(q,a)时需要的信息。由于数组π只有m个元素,而δ有Θ(m∣Σ∣)个值,所以通过预先计算π而不是δ,使得时间减少了一个Σ因子。
KMP实现
计算出一个
然后匹配时就利用
代码:
fo(i,1,n){ while(len&&s[len+1]!=s[i])len=next[len]; if (s[len+1]==s[i])len++; next[i]=len;}fo(i,1,n){ while(ans&&s[ans+1]!=p[i])ans=next[ans]; if (s[ans+1]==p[i])ans++; cout<<ans<<endl;}
Trie
在计算机科学中,
Trie实现
每次插入一个串
代码:
fo(i,1,len){ if(!a[now].to[s[i]])a[now].to[s[i]]=++tot; now=a[now].to[s[i]];}
复习了前面两个关于字符串的算法,我们可以开始讲AC自动机了。
AC自动机,实际上就是KMP和Trie的合体版。
KMP是单模式匹配,而AC自动机是多模式匹配。
主要思路
- 我们把所有的模式串插入到Trie中。
- 我们定义一个
fail 数组,与KMP的next 类似,faili 指向一个节点,使得从根节点到faili 的字符串是根节点到i 的后缀,且长度最长。 - 我们可以用类似KMP的方法用BFS求出
fail 数组。 - 最后就可以进行匹配了,从根节点开始走,如果匹配不了就跳到当前点的
fail ,知道可以匹配,然后就向下走。
模版:
#include <iostream>using namespace std;struct node{ int next[26]; int fail; int count; void init() { memset(next, -1, sizeof(next)); fail = 0; count = 0; }}s[500005];int sind;char str[55];char des[1000005];int q[500005], qin, qout;void cas_init(){ s[0].init(); sind = 1;}void ins(){ int len = strlen(str); int i, j, ind; for(i = ind = 0; i < len; i++) { j = str[i] - 'a'; if(s[ind].next[j] == -1) { s[sind].init(); s[ind].next[j] = sind++; } ind = s[ind].next[j]; } s[ind].count++;}void make_fail(){ qin = qout = 0; int i, ind, ind_f; for(i = 0; i < 26; i++) { if(s[0].next[i] != -1) { q[qin++] = s[0].next[i]; } } while(qin != qout) { ind = q[qout++]; for(i = 0; i < 26; i++) { if(s[ind].next[i] != -1) { q[qin++] = s[ind].next[i]; ind_f = s[ind].fail; while(ind_f > 0 && s[ind_f].next[i] == -1) ind_f = s[ind_f].fail; if(s[ind_f].next[i] != -1) ind_f = s[ind_f].next[i]; s[s[ind].next[i]].fail = ind_f; } } }}int fd(){ int ct = 0; int di, i, ind, p; int len = strlen(des); for(di = ind = 0; di < len; di++) { i = des[di] - 'a'; while(ind > 0 && s[ind].next[i] == -1) ind = s[ind].fail; if(s[ind].next[i] != -1) { ind = s[ind].next[i]; p = ind; while(p > 0 && s[p].count != -1) { ct += s[p].count; s[p].count = -1; p = s[p].fail; } } } return ct;}int main(){ int cas, n; scanf("%d", &cas); while(cas-- && scanf("%d", &n)) { gets(str); cas_init(); while(n-- && gets(str)) ins(); make_fail(); gets(des); printf("%d\n", fd()); } return 0;}#include <iostream>using namespace std;struct node{ int next[26];//每一个节点可以扩展到的字母 int fail;//每一个节点的失配指针 int count;//记录每一个可以构成单词的字符串距根节点的深度 void init()//构造 { memset(next, -1, sizeof(next));//初始化next为-1,即不与任何值相连 fail = 0;//失配指针为空 count = 0;//一开始没有单词赋为0 }}s[500005];int sind;//记录节点的编号char str[55];//模板串,”单词“char des[1000005];//”文章“int q[500005], qin, qout;//队列void cas_init()//在整个程序前构造root{ s[0].init();//初始化头结点 sind = 1;//当前有一个节点}void ins()//向书中插入字母{ int len = strlen(str);//模板串的长度 int i, j, ind; for(i = ind = 0; i < len; i++) { j = str[i] - 'a';//求出字母在next中的编号 if(s[ind].next[j] == -1)//如为空则构造新的,如不为空则顺着上次的开始往下走构造 { s[sind].init();//初始化当前节点 s[ind].next[j] = sind++;//连向当前节点,并使sind加一来扩充节点 } ind = s[ind].next[j];//向下遍历 } s[ind].count++;//增加离根节点这条路径上字符串的个数,一条路上可能不止一个单词}void make_fail()//构造失配指针{ qin = qout = 0;//初始化队列 int i, ind, ind_f; for(i = 0; i < 26; i++) { if(s[0].next[i] != -1) { q[qin++] = s[0].next[i];//先考虑根节点,和根节点相连的都入队 } } while(qin != qout) { ind = q[qout++];//记录队首节点 for(i = 0; i < 26; i++)//遍历队首节点的next { if(s[ind].next[i] != -1)//如果节点next不为空 { q[qin++] = s[ind].next[i];//将儿子节点入队 ind_f = s[ind].fail;//记录节点的失配指针指向 while(ind_f > 0 && s[ind_f].next[i] == -1)//当失配指针不为root时一直循环直到找到一个节点的儿子是i值或到了root ind_f = s[ind_f].fail; if(s[ind_f].next[i] != -1)//如果当前节点有儿子的话记录下来备用 ind_f = s[ind_f].next[i]; s[s[ind].next[i]].fail = ind_f;//使当前节点的失配指针指向刚才记录的节点完成失配指针的寻找构造。 } } }}int fd(){ int ct = 0;//记录“单词的个数” int di, i, ind, p;//di为指向“文章”的指针,ind为指向失配节点的指针(即trie树中自匹配的指针)与kmp中next数组中的temp很相似 int len = strlen(des);//“文章的长度” for(di = ind = 0; di < len; di++) { i = des[di] - 'a'; while(ind > 0 && s[ind].next[i] == -1)//当ind指针不是root和找不到节点的儿子是i时一直找下去(即kmp中的while循环) ind = s[ind].fail;//一直寻找失配指针 if(s[ind].next[i] != -1)//找到了适合的失配指针 { ind = s[ind].next[i];//指向这个儿子节点,更新ind的值进行下一次匹配 p = ind;//用p来临时代替ind while(p > 0 && s[p].count != -1)//p > 0表示还没到root,count != -1表示指针前还有单词 { ct += s[p].count;//加上有的单词的个数 s[p].count = -1;//不重复计算,注意这里很重要 p = s[p].fail;//一直寻找失配指针 } } } return ct;//返回单词个数}int main(){ int cas, n; scanf("%d", &cas); while(cas-- && scanf("%d", &n)) { gets(str); cas_init();//初始化trie树 while(n-- && gets(str)) ins();//构造trie树 make_fail();//构造失配指针 gets(des); printf("%d\n", fd()); } return 0;}
- 浅谈AC自动机
- 浅谈AC自动机(Aho-Corasick automaton算法)
- AC自动机...
- AC自动机
- AC 自动机
- AC自动机
- AC自动机
- ac自动机
- ac自动机
- AC自动机
- AC自动机
- AC自动机
- AC自动机
- AC自动机
- AC 自动机
- ac自动机
- AC自动机
- AC自动机
- 1024. Palindromic Number (25)
- POJ 2318 TOYS(点与凸多边形的位置关系)
- caffe卷积层代码阅读笔记
- 算法竞赛入门经典:第六章 数据结构基础 6.6层次遍历
- POJ3281 Dining 求最大流
- 浅谈AC自动机
- WebBrowser控件使用详解
- 算法竞赛入门经典:第六章 数据结构基础 6.7层次遍历
- STM32F407VGT芯片的操作之流水灯
- 机房问题总结之改变select语句中的变量
- 【bzoj1143】 CTSC2008祭祀river 二分图匹配
- 液体效果,制作喷溅的液态裙子教程
- UVA 1160X-Plosives【并查集】
- hdu2899Strange fuction(三分搜索求极值)