Aho-Corasick Automaton · AC自动机

AC自动机是一个高效的字符串多模匹配算法,它的核心想法是把KMP的失配指针做到Trie上,从而实现对于所有模板字符串而言的单次文本串扫描出结果。由此可见,若要整下AC自动机,必须先掌握Trie和KMP。另外,AC自动机真的不是自动帮你AC的机子 其实你可以找AK自动机
注意,下文涉及的Trie以及KMP主要为AC自动机做铺垫,详细算法介绍请见相应文章!

·字典树(Trie)

其实字典树、前缀树什么的是同一个东西。

不难看出,树上每个节点都对应了一个单词。Trie就是这样一颗多叉树(26叉较常用,图中省略了空子树),它的边用一个大小为26的数组记录,这样就可以实现O(1)向下查找。例如单词“tea”,它对应树中路径root (第零层) > 1-1(第一层左往右第一个) > 2-2 > 3-1 。
插入:从根开始,顺着边往下走(O(1)递进,实现见代码),当走到词的末位时标记当前节点
查询:从根开始,顺着边往下走(中途走不通视为查询失败),当走到词的末位时检查当前节点,确认是否被标记。
实现方法:市面上 有两种:数组模拟和充斥着指针的动态实现,建议初学者使用静态数组,dalao们请随意

代码一言不合就封装

class Trie
{
private:class Nodes {public:Nodes* nxt[26];bool end;Nodes(){for(int i=0;i<26;++i)nxt[i]=NULL;end=false;}void init(){for(int i=0;i<26;++i)nxt[i]=NULL;end=false;}};Nodes root,*now;
public:Trie(){this->root.init();this->now=NULL;}void init(){this->root.init();this->now=NULL;}int insert(const char *s){if(!s)return 0;this->now=&this->root;for(int i=0;s[i];++i){char ch=(s[i]>='a'&&s[i]<='z'?s[i]-'a':s[i]-'A');//假定大小写通用//一般情况下这样写: char ch=s[i]-'a';if(!this->now->nxt[ch]){if((this->now->nxt[ch]=new Nodes)==NULL){return -1;//申请内存失败,返回错误信息}}this->now=this->now->nxt[ch];}this->now->end=true;//标记单词末尾return 1;}bool find(const char *s){this->now=&this->root;for(int i=0;s[i];++i){char ch=(s[i]>='a'&&s[i]<='z'?s[i]-'a':s[i]-'A'); if(!this->now->nxt[ch]){return false;}this->now=this->now->nxt[ch];}return this->now->end;}~Trie()//析构函数?并不会写qwq{}
};

·看毛片(KMP)

看毛片算法至关重要,请务必仔细阅读并理解相应的内容。

部分匹配值

· 我也不知道是不是这么叫,反正实际上是最长公共前后缀。
· 最长公共前后缀:(前缀和后缀的定义就不说了,如果不会,自行Ctrl+w) 找到max(len),使得字符串长度为len的前缀和后缀完全匹配。第三个栗子: ABCAB的最长公共前后缀为AB,len=2
算了还是说一下前缀和后缀吧:

ABCBCD
len     前缀         后缀0     (空串)       (空串)1     A            D2     AB           CD3     ABC          BCD4     ABCB         CBCD5     ABCBC        BCBCD......6     ABCBCD       ABCBCD
部分匹配值的获取(get_next函数)

· 惯例:如果你还不知道部分匹配值,请按Ctrl+w
next数组的意义:nxt[i]表示搜索词从开头(第零位)到第i位的部分匹配值

void mknxt(int nxt[],int lw,char w[])
{int k=0,i=1;//k是上一次的匹配值,i务必从1开始for(k=0,i=1;i<lw;++i){while(k>0&&w[i]!=w[k])//k>0不解释,w[i]!=w[k]即当前匹配不成功k=nxt[k-1];//我也不想解释,可就是难解释啊if(w[i]==w[k])//当前位置匹配成功k++;nxt[i]=k;}
}

当前位置匹配成功的操作很好理解,k+1即可。
关键在于不相等的情况:k=nxt[k-1]是什么鬼!


w[k]与w[i]不等的处理方法:显然长度为k的前后缀是不能用了,所以应当去考虑短一点的前后缀。展开图中大条子,出现上下两个小条子,小条子的结构也是绿黄绿,而且(1-1)=(1-2),(2-1)=(2-2)。我们知道(大条子绿色前缀)=(大条子绿色后缀),所以(1-1)=(1-2)=(2-1)=(2-2)。然后执行k=nxt[k-1],原图变为:

现在,大条子中的绿色部分已经缩短,k的值改变为原来的nxt[k-1]。再次检查w[k]和w[i]。

另外:
如果你实在看不懂我说的,或许这个能帮到你(下文的P数组即w数组):

上图转自博客园的 c_cloud ,他的文章参考
http://www.ruanyifeng.com/blog/2013/05/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm.html

查找
void KMP()
{for(int i=0,k=0;i<ls;++i){while(k>0&&w[k]!=s[i])k=nxt[k-1];if(w[k]==s[i])k++;if(k==lw)printf("Pattern occurs with shift:%d\n",(i-lw+1));}
}

如果你看懂了next数组的构造,这里也就不需要解释了

·正片-AC自动机

当需要找的东西太多时,KMP就显得很磨叽(需要对每一个模板串构造一次nxt数组并在所有文本串中找一次),因此引入AC自动机。

# 构造Trie

将模板串全部读入并构造Trie,这里需要在之前的Trie基础上新增last和fail指针,前者叫做后缀链接(后头再解释),后者就是失配边。

# 画图构造失配边与后缀链接

失配边类似于KMP中的next,实际上指向了一个串的后缀(只需要满足后缀的关系就行),有了这玩意儿可以极大的方便查找;

后缀链接指向串的后缀(废话)且该后缀是一个完整的单词。后缀链接的作用很显然,如果有{“fubuki”,”ki”},某时刻已经找到了”fubuki”,那么顺着后缀链接就能找到”ki”,这样一来,后缀链接就可以进行递归累计。

void ACA()//BFS构造失配边
{queue<Trie*>que;for(int i=0;i<26;++i){now=root.nxt[i];if(now){que.push(now);now->f=&root;}}//初始化队列while(!que.empty()){//逐层构造Trie *hed=que.front();que.pop();for(int i=0;i<26;++i){//构造对于now->nxt[i]的fail与lastTrie *j=hed->f;now=hed->nxt[i];if(!now){if(j)hed->nxt[i]=j->nxt[i];//这句过会儿解释,请暂忽略continue;}que.push(now);while(j&&!j->nxt[i])//从hed->f的基础上获取hed->nxt[i]->f,找一个hed的后缀,且这个后缀的后一个字符为ij=j->f;
/* <???>"*********"'字符' hed->nxt[i]对应的串(双引号内为已经确定fail的部分)和当前字符"*********"'未知字符' hed->f对应的串及其下一个字符(未知)显然,两端中双引号内字符串完全匹配,只需考虑二者的下一个字符是否相等
*/if(j)//数组版无需判断,这里是指针防越界(空指针){now->f=j->nxt[i];now->lst=now->f->flg?now->f:now->f->lst;//如果失配边指向一个单词就直接记录lst,否则看看失配边指向的lst}if(!now->f)//空串是任意串的后缀,无法构造fail的就指回root Ps.数组版还是不需要。。。now->f=&root;}}
}
# 匹配(查询)

与KMP的Find相差无几,只不过由推动下标变为推动节点指针

void find()
{now=&root;for(int i=0;s[i];++i){int j=s[i]-'a';while(now&&!now->nxt[j])//如果当前节点没有儿子i就顺着失配边走直到新节点有儿子i或走回rootnow=now->f;if(now)//再次防越界(空指针){now=now->nxt[j];//相当于KMP里的指针后移if(now->flg)//当前节点记录了单词count(now);//递归累加出现次数else if(now->lst)//当前串的后缀可能是个单词count(now->lst);}else//now为空意味着走回rootnow=&root;}return;
}
/*构造失配边时有一句:hed->nxt[i]=j->nxt[i];
实际上是加上一条不存在的边,把nxt[i]变成了另一个fail指针
如果构造时写了这句话,匹配的时候就无需写while(now&&!now->nxt[j])了*/
void count(Trie *now)
{if(!now)return;cnt[now->flg]++;count(now->lst);//当前串的后缀可能又是一个单词
}

最后贴上完整代码,写的很粗鲁大家见谅
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>using namespace std;int n,m,cnt[1005];
char s[1005];//不要在意大小class Trie
{
public:Trie *nxt[26],*f,*lst;int flg;Trie(){for(int i=0;i<26;++i)nxt[i]=NULL;f=lst=NULL;flg=0;}
}root,*now;//Trievoid count(Trie *now)
{if(!now)//return;cnt[now->flg]++;count(now->lst);
}//累计答案用的,不同题目会要求不同答案void find()
{now=&root;for(int i=0;s[i];++i){int j=s[i]-'a';while(now&&!now->nxt[j])now=now->f;if(now){now=now->nxt[j];if(now->flg)count(now);else if(now->lst)count(now->lst);}elsenow=&root;}
}void ACA()//BFS构造失配边
{queue<Trie*>que;for(int i=0;i<26;++i){now=root.nxt[i];if(now){que.push(now);now->f=&root;}}while(!que.empty()){Trie *hed=que.front();que.pop();for(int i=0;i<26;++i){Trie *j=hed->f;now=hed->nxt[i];if(!now){if(j)hed->nxt[i]=j->nxt[i];continue;}que.push(now);while(j&&!j->nxt[i])j=j->f;if(j){now->f=j->nxt[i];now->lst=now->f->flg?now->f:now->f->lst;}if(!now->f)now->f=&root;}}
}int main()
{freopen("test.in","r",stdin);freopen("test.out","w",stdout);scanf("%d%d",&n,&m);for(int i=1;i<=n;++i){now=&root;scanf("%s",s);for(int j=0;s[j];++j){if(!now->nxt[s[j]-'a'])now->nxt[s[j]-'a']=new Trie;now=now->nxt[s[j]-'a'];}now->flg=i;}ACA();for(int i=1;i<=m;++i){scanf("%s",s);find();}for(int i=1;i<=n;++i)printf("%d ",cnt[i]);return 0;
}
·写在后边

如果哪一位dalao会 AK自动机 请尽快联系本蒟蒻,必有重谢!
本文多有不妥之处,欢迎批评指正!
本文不会有人转的。。。转载请注明出处。

参考:
刘汝佳《算法竞赛入门经典(训练指南)》
徐玄長(好吧我自己)《KMP·看毛片》

Aho-Corasick Automaton · AC自动机相关推荐

  1. 浅谈Aho-Corasick automaton(AC自动机)

    Aho-Corasick automaton是什么? 要学会AC自动机,我们必须知道什么是Trie,也就是字典树.Trie树,又称单词查找树或键树,是一种树形结构,是一种哈希树的变种.典型应用是用于统 ...

  2. ac自动机 匹配最长前缀_Aho Corasick自动机结合DoubleArrayTrie极速多模式匹配

    本文使用Double Array Trie实现了一个性能极高的Aho Corasick自动机,应用于分词可以取得1400万字每秒,约合27MB/s的分词速度.其中词典为150万词,构建耗时1801 m ...

  3. Aho-Corasick automaton,ac自动机实现

    文章目录 写在前面 算法概述 trie树的构建 trie树的节点结构 插入P串到trie树中 fail指针的创建 搜索过程 测试程序 写在前面 原作者的视频讲解链接:[算法]轻松掌握ac自动机_哔哩哔 ...

  4. AC自动机——Aho-Corasick Automaton

    这是一个英文版的讲的比较好的AC自动机资料. http://www.cs.uku.fi/~kilpelai/BSA05/lectures/slides04.pdf 如果不爱看英文,可以看我整理的大致的 ...

  5. Aho-Corasick 多模式匹配算法(AC自动机) 的算法详解及具体实现

    多模式匹配 多模式匹配就是有多个模式串P1,P2,P3-,Pm,求出所有这些模式串在连续文本T1-.n中的所有可能出现的位置. 例如:求出模式集合{"nihao","ha ...

  6. TypeScript:Aho–Corasick算法实现敏感词过滤

    敏感词过滤应该是许多后端同事经常会遇到的需求,无论是评论.弹幕.文章,都需要做敏感词过滤处理来规避风险.在前端开发中,使用replace函数来替换字符串是我们的常规操作,在这之前我思考过如果用Java ...

  7. AC自动机算法详解以及Java代码实现

    详细介绍了AC自动机算法详解以及Java代码实现. 文章目录 1 概念和原理 2 节点定义 3 构建Trie前缀树 3.1 案例演示 4 构建fail失配指针 4.1 案例演示 5 匹配文本 5.1 ...

  8. 一本通提高篇 AC自动机

    本来想写树状数组的 好像mymymy申对这棵树有点迷茫 不过他不回我 那就接着来AVC自动机吧 UPD:20200517UPD:20200517UPD:20200517期中考试考完了 确实考完了-这辈 ...

  9. POJ 2778 DNA Sequence —— (AC自动机+矩阵快速幂)

    距离上次做AC自动机有很久了=.=,以前这题的思路死活看不懂,现在还是觉得很好理解的. 思路参见:http://blog.csdn.net/morgan_xww/article/details/783 ...

  10. CDOJ1633 Video Game Combos [AC自动机+dp]

    题目地址:http://acm.uestc.edu.cn/problem.php?pid=1633 AC自动机+BFS AC自动机,参见:http://www.cnblogs.com/luna-lov ...

最新文章

  1. 1.3 使用jmeter进行http接口测试
  2. 源文件的编码会对编译结果有影响
  3. time包中Parse和Format的区别
  4. 【POJ - 3126】Prime Path(bfs)
  5. VirtualBox安装Centos6.8出现——E_INVALIDARG (0x80070057)
  6. java 类 加载 初始化_java中类的初始化和加载
  7. JAVA爬取亚马逊的商品信息
  8. 【项目合作】移动端人体姿态估计
  9. 谷歌开源语音识别AI技术,可以从人群中区分每个人的发言
  10. servlet向ajax传递数据库,一、JSP、servlet、SQL三者之间的数据传递(前台与后台数据交互)...
  11. jvm内存分析和cpu耗时分析
  12. VC 2012 中调用WebBrowser简单的实现过程(图解过程)
  13. java mail 20m附件,发送邮件时附件大小不能超过20M,否则无法发送
  14. 关于ancher box 和bounding box的区别
  15. mysql服务器存储视频文件,把视频文件直接存储到mysql数据库的方法
  16. html图片动态案例,10个强大的纯CSS3动画案例分享
  17. 崩溃,我带的实习生把图片直接存到了服务器上
  18. 什么是信息增益(Information Gain)?
  19. 《网络攻防》信息搜集与漏洞扫描
  20. java.lang.CloneNotSupportedException: com.lbh.xxmanager.basic.alg.Node at java.lang.Object.clone

热门文章

  1. Git 基础知识 - 查看提交历史记录
  2. xp系统遭遇STOP 0X0000007B蓝屏,附解决方案
  3. 创新设计思维总结报告
  4. UESTC 1633 去年春恨却来时,落花人独立,微雨燕双飞 Dijkstra+构造
  5. 【CF #807 Div2】A-D
  6. python输出冒号_详谈python中冒号与逗号的区别_python_脚本之家
  7. ESP32 LVGL8.1 ——Style Text 设置文字样式 (Style 8)
  8. 计算机毕业论文外文翻译是什么,毕业论文的外文翻译是什么
  9. 覆了天下也罢,始终不过一场繁华
  10. MSP430 MSP430单片机输入/输出模块 通用I/O端口GPIO