博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
简单版AC自动机
阅读量:4705 次
发布时间:2019-06-10

本文共 2587 字,大约阅读时间需要 8 分钟。

简单版\(AC\)自动机

学之前听别人说起一直以为很难,今天学了简单版的\(AC\)自动机,感觉海星,只要理解了\(KMP\)一切都好说。

前置知识:(有链接)

前置知识:\(Trie\)

字典树(\(Trie\)树)比较简单,就是把许多个单词通过树连接起来。每个点记录一下儿子个数以及是否是单词结尾即可。每次加入一个单词时,从第一个字母开始搜索,如果当前字母存在,就从该字母的儿子里找下一个字母,否则就新建一个节点,直到把这个单词全部加入进去,然后在最后的字符上标记一下表示以这个字母结尾的单词多了一个。

那么\(AC\)自动机实际上就是将两者合并了起来,在字典树上进行\(KMP\)

先说一下\(AC\)自动机是干什么的。一个常见的例子就是给出n个单词,再给出一段包含m个字符的文章,让你找出有多少个单词在文章里出现过。要搞懂AC自动机,先得有模式树(字典树)\(Trie\)\(KMP\)模式匹配算法的基础知识。\(KMP\)算法是单模式串的字符匹配算法,\(AC\)自动机是多模式串的字符匹配算法。

说白了就是给你一堆字符串,然后再给你一个字符串,问最后这个字符串中出现了多少个前面给出的字符串。

首先我们要有一个字典树。对于给出的那一堆字符串,我们要一个一个加到树里。代码如下:

struct AC{//字典树    int end,vis[26],fail;//vis表示儿子的编号}AC[1000006];int cnt;void Build(string s){//要加入的单词    int l=s.length(),now=0;//now是当前节点    for(int i=0;i

有了字典树,考虑怎样在树上进行\(KMP\)

\(KMP\)里面的\(next\)指针在这里改成\(fail\),其实都一样。

每个节点\(t\)\(fail\)指针,其所指向的节点和\(t\)节点的字符是一样的。因为如果\(t\)匹配成功,而\(t\)的儿子匹配失败,那么需要从\(t\)\(fail\)指针的儿子节点开始匹配。

\(fail\)指针用\(BFS\)来求。

首先,根节点的\(fail\)指针显然指向他自己,即\(0\)。而他的儿子,也就是深度为一的节点的指针也是指向他的。那么考虑剩下的节点\(t\)。它的父亲节点的\(fail\)指针已经知道,那么这个指针指向的节点假如是\(u\)的话,如果\(u\)有一个和\(t\)一样的节点,那么\(t\)\(fail\)指针就应该指向它,如果没有,就要从\(father->fail->fail\)里找,直到找到相同的节点或者到根节点。也就是说要顺着之前的失配指针走一遍,有点麻烦。

考虑如果当前节点没有某个字母,那么我们可以将该节点指向这个字母的指针,指到他的失配指针指向的节点的这个字母上。

if(AC[u].vis[i]==0)    AC[u].vis[i]=AC[AC[u].fail].vis[i];

这样就不用沿着失配指针走一遍了。代码如下:

void Get_fail(){    queue
Q;//队列,bfs for(int i=0;i<26;++i)//处理深度为二的点 if(AC[0].vis[i]) AC[AC[0].vis[i]].fail=0,Q.push(AC[0].vis[i]); while(!Q.empty()){ int u=Q.front(); for(int i=0;i<26;++i) if(AC[u].vis[i]) AC[AC[u].vis[i]].fail=AC[AC[u].fail].vis[i],Q.push(AC[u].vis[i]); //如果有这个点,就直接更新指针并压入队列 else AC[u].vis[i]=AC[AC[u].fail].vis[i];//没有就按上述方法处理 Q.pop(); }}

最后就是统计了。

对于每个字母,如果他是几个单词的结尾,那么久加上他的以及他的所有失配指针的答案,因为他可以,他的失配指针同样可以。

代码:

int AC_query(string s){    int l=s.length(),now=0,ans=0;    for(int i=0;i

\(Code\)

#include
#include
#include
#include
using namespace std;struct AC{ int end,vis[26],fail;}AC[1000006];int cnt;void Build(string s){ int l=s.length(),now=0; for(int i=0;i
Q; for(int i=0;i<26;++i) if(AC[0].vis[i]) AC[AC[0].vis[i]].fail=0,Q.push(AC[0].vis[i]); while(!Q.empty()){ int u=Q.front(); for(int i=0;i<26;++i) if(AC[u].vis[i]) AC[AC[u].vis[i]].fail=AC[AC[u].fail].vis[i],Q.push(AC[u].vis[i]); else AC[u].vis[i]=AC[AC[u].fail].vis[i]; Q.pop(); }}int AC_query(string s){ int l=s.length(),now=0,ans=0; for(int i=0;i
>n; for(int i=1;i<=n;++i) cin>>s,Build(s); AC[0].fail=0; Get_fail(); cin>>s; cout<

转载于:https://www.cnblogs.com/kylinbalck/p/10424306.html

你可能感兴趣的文章
一点小基础
查看>>
UNET学习笔记2 - 高级API(HLAPI)
查看>>
"ORA-00942: 表或视图不存在 "的原因和解决方法[转]
查看>>
Oauth支持的5类 grant_type 及说明
查看>>
C#中用DateTime的ParseExact方法解析日期时间(excel中使用系统默认的日期格式)
查看>>
W3100SM-S 短信猫代码发送 上
查看>>
Linux IO模式及 select、poll、epoll详解
查看>>
Log4j知识汇总
查看>>
python生成.exe文件
查看>>
PHP面向对象(OOP)----分页类
查看>>
监听SD卡状态
查看>>
vs2017 EFCore 迁移数据库命令
查看>>
serialVersionUID的作用
查看>>
liunx trac 插件使用之GanttCalendarPlugin
查看>>
(14)嵌入式软件开发工程师技能要求总结
查看>>
[hackerrank]Closest Number
查看>>
volatile关键字
查看>>
[Android] TabLayout设置下划线(Indicator)宽度
查看>>
<潭州教育>-Python学习笔记@条件与循环
查看>>
web自动化之验证码识别解决方案
查看>>