国庆后面两天划水,甚至想接着发出咕咕咕的叫声。咳咳咳,这些都不重要!最近学习了一下AC自动机,发现其实远没有想象中的那么难。

AC自动机的来历

我知道,很多人在第一次看到这个东西的时侯是非常兴奋的。(别问我为什么知道)

但AC自动机并不是能自动AC的程序。。。

AC自动机之所以叫AC自动机,是因为这个算法原名叫 Aho-Corasick automaton,是一个叫Aho-Corasick 的人发明的。

所以AC自动机也叫做 Aho-Corasick 算法

该算法在1975年产生于贝尔实验室,是著名的多模匹配算法。

AC自动机的用处

那么有的同学可能就有疑问了,AC自动机又不能自动AC,有什么作用呢?

其实AC自动机和KMP的用法相似,都是用来解决字符串的匹配问题的;但不一样的是,AC自动机更多的被用来解决多串的匹配问题,换言之,就是有多个子串需要匹配的KMP问题。

例如,例如给几个单词 acbs,asf,dsef;

再给出一个 很长的文章(句子),acbsdfgeasf

问在这个文章中,总共出现了多少个单词,或者是单词出现的总次数,这就是AC自动机要解决的问题了。

AC自动机的实现方法

AC 自动机是 以 Trie 的结构为基础 ,结合 KMP 的思想 建立的。

简单来说,建立一个 AC 自动机有两个步骤:

  1. 基础的 Trie 结构:将所有的模式串构成一棵 Trie
  2. KMP 的思想:对 Trie 树上所有的结点构造失配指针。

然后就可以利用它进行多模式匹配了。

不明白trie的同学可以 点击这里学习

不了解KMP的同学可以点击这里学习

所以就让我们一起来一步一步实现AC自动机吧!

定义一颗字典树

首先我们需要定义一颗字典树,我们用struct来实现各个节点的定义:

struct node
{
int next[27];
int fail;
int count;
void init()
{
memset(next,-1,sizeof(next));
fail=0;
count=0;
}
}s[1100001];

存储后驱值的next[]数组

next[]数组就是正常Trie树里用来存储每个字符的后一个字符在s数组里的位置,比如我们读入一个字符串APPLE,那么:

s【1】存储的是A,它的next【P】=2,其余为-1;

s【2】存储的是P,它的next【P】=3,其余为-1;

s【3】存储的是P,它的next【L】=4,其余为-1;

s【4】存储的是L,它的next【E】=5,其余为-1;

s【5】存储的是E,它的next都为-1。

fail:失败指针

fail为失败指针,这个在后面的构造会讲到如何快速构造,那么有什么用呢?

我们来举个例子,这个例子这只显示了e的失配指针:

我们假设读入了she,shr,say,her四个单词,于是我们就得到了一棵可爱的字典树:

然后我们就只先构造一个失败指针:

例如匹配文章:sher,我们刚开始从s开始一直向左边走,走到e后发现:呀,没有路继续走了,如果暴力的从h开始又开始一轮匹配就极为浪费时间;这时我们就像,能不能利用之前的匹配信息呢?可以的!her的前缀he刚好和she的he相同,所以我们在she匹配失败的时候,就跳到了he后面继续匹配,发现r与r匹配!这就是fail指针的用处,是不是发现和KMP的next数组非常类似啊!

记录结尾的count

如果我插入一个单词APPLE,插入到最后E了,发现这个单词再也没有后面的字母了,这时我们就在这个E的count里面加上一个1,表示有1个单词以这个e作为结尾。

初始化的init()

我们在这里还定义了一个初始化函数init(),就是在开创到一个新起点时用来初始化一下的。

在字典树中插入单词

我们还是结合程序来讲解:

int ins()
{
int len=strlen(str+1);
int ind=0,i,j; for(int i=1;i<=len;i++)
{
j=str[i]-'a'+1;
if(s[ind].next[j]==-1)
{
num++;
s[num].init();
s[ind].next[j]=num;
}
ind=s[ind].next[j]; }
s[ind].count++;
return 0;
}

首先str数组就是我们要读入的字符串,ind表示我现在在s【】数组中的位置;接下来我们开始循环——对于每一个点:

如果他的前一个字母的next没有指向他的字母,那么我们就开创一个新点来存储这个字母,并且让他前一个字母的next指向它;

如果有直接指向它的字母的位置,那就直接跳过去就好了!

最后别忘了在每个单词的末尾的count加上1。

重点!!!快速构造fail指针

fail指针有什么用

首先,fail指针有什么用?我们继续使用上一个例子:

我们发现,左边的e的fail指针指向l最右侧的e,那么这个指针的含义是什么呢?我们不妨当一个点i指向了一个点j时,我们设从j开始,向上走L个字符就到了最顶点,其中从顶点到j的字符串为s;

在这个例子中,s为“he”,长度为L,也就是2;接着从i开始,向上再走L-1个字符,得到一个字符串ss,在这个例子中,ss也为“he”!

这时我们就惊讶的发现,s与ss相同!!

我们得知,当i的fail指针指向j,顶点到j的字符串s是顶点到i的字符串的后缀!

这样如果i继续往下匹配失败的话,就可以不用从头开始匹配,而是直接从他的fail开始匹配!节省了大量时间!这就是fail指针的精髓所在!

fail指针如何构造

我们先贴上代码:

int make_fail()
{
int head=1,tail=1;
int ind,ind_f;
for(int i=1;i<=26;i++)
{
if(s[0].next[i]!=-1)
{
q[tail]=s[0].next[i];
tail++;
}
}
while(head<tail)
{
ind=q[head];
for(int i=1;i<=26;i++)
{
if(s[ind].next[i]!=-1)
{
q[tail]=s[ind].next[i];
tail++; 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;
}
}
head++;
}
return 0;
}

首先我们需要开启一个队列q,存储需要处理的点;

接着我们把所有与顶点相连的点加入到队列里,然后我们对于队列里的每个数进行操作:

首先将他的所有儿子都加到队列尾部,然后作为一个负责任的父亲节点,肯定不能只把儿子们丢到队尾就完事了,还有做好工作——帮儿子们做好fail指针——

首先假如我是那个父亲节点x,对于字母a子节点,我先看一下我的fail指针指向的节点y,看一下y有没有字母a子节点z,如果有,就太好了,我就让我的子节点的fail指针指向z;

如果没有,那就从y出发,继续看他fail指向的点的有没有字母a的子节点……直到找到满足条件的点。

如果实在没办法,就算fail一路跳到0号节点也找不到,那就没办法了,我的字母a子节点的fail就只好指向0号节点了【因为初始化就为0,所以此时就不用操作了】

我们举个具体的栗子来看看:





















所以这样操作就可以快速构造fail指针了!

进行树上KMP

我们先看一下代码:

int find()
{
int len=strlen(des+1);
int j,ind=0;
for(int i=1;i<=len;i++)
{
j=des[i]-'a'+1;
while(ind>0 && s[ind].next[j]==-1)ind=s[ind].fail; if(s[ind].next[j]!=-1)
{
ind=s[ind].next[j];
p=ind;
while(p>0 && s[p].count!=-1)
{
ans=ans+s[p].count;
s[p].count=-1;
p=s[p].fail;
}
}
}
return 0;
}

一样的,ind表示我当前匹配好了的点,如果当前点不继续和IND的任何一个子节点相同,那么我就跳到ind的fail指针指向的点……知道找到与当前点匹配,或者跳到了根节点,与KMP十分相同!

需要注意的是由于这道题是求解哪些点在母串中出现,所以我们进行了一层优化:

while(p>0 && s[p].count!=-1)
{
ans=ans+s[p].count;
s[p].count=-1;
p=s[p].fail;
}

就是当我们匹配好到一个串s【从根节点到IND的串】的时候,我们就往它的fail一直跳,由于他的fail到根节点的字符串ss一定是s的后缀,所以ss在母串中也一定出现,这时就加上它的count再设置为-1,防止后续重复访问就好了!

模板题

[Luogu p3808]

题目背景

这是一道简单的AC自动机模板题。

用于检测正确性以及算法常数。

为了防止卡OJ,在保证正确的基础上只有两组数据,请不要恶意提交。

管理员提示:本题数据内有重复的单词,且重复单词应该计算多次,请各位注意

题目描述

给定n个模式串和1个文本串,求有多少个模式串在文本串里出现过。

输入输出格式

输入格式:

第一行一个n,表示模式串个数;

下面n行每行一个模式串;

下面一行一个文本串。

输出格式:

一个数表示答案

输入输出样例

输入样例#1: 复制

2

a

aa

aa

输出样例#1: 复制

2

说明

subtask1[50pts]:∑length(模式串)<=10^6,length(文本串)<=10^6,n=1;

subtask2[50pts]:∑length(模式串)<=10^6,length(文本串)<=10^6;

就是模板题,下面给出模板:

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
using namespace std;
struct node
{
int next[27];
int fail;
int count;
void init()
{
memset(next,-1,sizeof(next));
fail=0;
count=0;
}
}s[1100001]; int i,j,k,m,n,o,p,js,jl,jk,len,ans,num;
char str[1100000],des[1100000];
int q[1100000]; int ins()
{
int len=strlen(str+1);
int ind=0,i,j; for(int i=1;i<=len;i++)
{
j=str[i]-'a'+1;
if(s[ind].next[j]==-1)
{
num++;
s[num].init();
s[ind].next[j]=num;
}
ind=s[ind].next[j]; }
s[ind].count++;
return 0;
} int make_fail()
{
int head=1,tail=1;
int inf,inf_f;
for(int i=1;i<=26;i++)
{
if(s[0].next[i]!=-1)
{
q[tail]=s[0].next[i];
tail++;
}
}
while(head<tail)
{
inf=q[head];
for(int i=1;i<=26;i++)
{
if(s[inf].next[i]!=-1)
{
q[tail]=s[inf].next[i];
tail++; inf_f=s[inf].fail; while(inf_f>0 && s[inf_f].next[i]==-1)
inf_f=s[inf_f].fail; if(s[inf_f].next[i]!=-1)inf_f=s[inf_f].next[i];
s[s[inf].next[i]].fail=inf_f;
}
}
head++;
}
return 0;
} int find()
{
int len=strlen(des+1);
int j,ind=0;
for(int i=1;i<=len;i++)
{
j=des[i]-'a'+1;
while(ind>0 && s[ind].next[j]==-1)ind=s[ind].fail; if(s[ind].next[j]!=-1)
{
ind=s[ind].next[j];
p=ind;
while(p>0 && s[p].count!=-1)
{
ans=ans+s[p].count;
s[p].count=-1;
p=s[p].fail;
}
}
}
return 0;
} int main()
{
scanf("%d",&m); num=0;s[0].init();
for(int i=1;i<=m;i++)
{
scanf("%s",str+1);
ins();
} scanf("%s",des+1); ans=0; make_fail(); find(); printf("%d",ans);
return 0;
}

结语

通过这篇博客相信你一定已经学会了AC自动机!希望你喜欢这篇blog!!!

【字符串算法】AC自动机的更多相关文章

  1. 字符串处理-AC自动机

    估计在OJ上刷过题的都会对AC自动机这个名词很感兴趣,同样,记得去年ACM暑期集训的时候,在最后讲到字符串部分,听说了这个算法的名字之后就对于它心向往之,AC正好是Accept的简称,字面意义上的理解 ...

  2. HDU-2222 Keywords Search 字符串问题 AC自动机

    题目链接:https://cn.vjudge.net/problem/HDU-2222 题意 给一些关键词,和一个待查询的字符串 问这个字符串里包含多少种关键词 思路 AC自动机模版题咯 注意一般情况 ...

  3. 2017ACM暑期多校联合训练 - Team 8 1006 HDU 6138 Fleet of the Eternal Throne (字符串处理 AC自动机)

    题目链接 Problem Description The Eternal Fleet was built many centuries ago before the time of Valkorion ...

  4. 浅谈算法——AC自动机

    在学习AC自动机之前,你需要两个前置知识:Trie树,KMP 首先我们需要明白,AC自动机是干什么的(用来自动AC的) 大家都知道KMP算法是求单字符串对单字符串的匹配问题的,那么多字符在单字符上匹配 ...

  5. 字符串(AC自动机):HDU 5129 Yong Zheng's Death

    Yong Zheng's Death Time Limit: 20000/10000 MS (Java/Others)    Memory Limit: 512000/512000 K (Java/O ...

  6. HDU-2896 病毒侵袭 字符串问题 AC自动机

    题目链接:https://cn.vjudge.net/problem/HDU-2896 题意 中文题 给一些关键词和一个字符串,问字符串里包括了那几种关键词 思路 直接套模版 改insert方法,维护 ...

  7. HDU-3065 病毒侵袭持续中 字符串问题 AC自动机

    题目链接:https://cn.vjudge.net/problem/HDU-3065 题意 跟上一道题是几乎一模一样,这次是统计关键词的出现次数 一个相当坑的地方,注意多组样例 思路 套模版 改in ...

  8. 字符串(AC自动机):COCI 2015 round 5 divljak

    aaarticlea/png;base64,iVBORw0KGgoAAAANSUhEUgAAAy0AAANaCAIAAAALVTQoAAAgAElEQVR4nOy9X2hbx773PXfrQgQjDq

  9. 字符串:AC自动机

    给出一个字典和一个模式串,问模式串中出现几个字典中的单词 最后一行是大串,之前输入的是小串 #include<iostream> #include<cstdio> using ...

  10. AC自动机——多模式串匹配的算法思想

    标准KMP算法用于单一模式串的匹配,即在母串中寻求一个模式串的匹配,但是现在又存在这样的一个问题,如果同时给出多个模式串,要求找到这一系列模式串在母串存在的匹配个数,我们应该如何处理呢? 基于KMP算 ...

随机推荐

  1. 2020,8种必备Selenium编写自动化用例的技巧

    在开始自动化时,您可能会遇到各种可能包含在自动化代码中的方法,技术,框架和工具.有时,与提供更好的灵活性或解决问题的更好方法相比,这种多功能性导致代码更加复杂.在编写自动化代码时,重要的是我们能够清楚 ...

  2. Python 批量下载BiliBili视频 打包成软件

    文章目录 很多人学习python,不知道从何学起.很多人学习python,掌握了基本语法过后,不知道在哪里寻找案例上手.很多已经做案例的人,却不知道如何去学习更加高深的知识.那么针对这三类人,我给大家 ...

  3. Java Android程序员软件开发知识:枚举的介绍,以及代码的编写教程。

    Java枚举 Java 5.0引入了枚举,枚举限制变量只能是预先设定好的值.使用枚举可以减少代码中的bug. 例如,我们为果汁店设计一个程序,它将限制果汁为小杯.中杯.大杯.这就意味着它不允许顾客点除 ...

  4. react-native 常用命令

    创建项目 react-native init AwesomeProject //AwesomeProject是项目名 启动 Node.js web server react-native start ...

  5. org.xml.sax.SAXParseException: The processing instruction target matching "[xX][mM][lL]" is not allowed(转)

    xml文件不能被正确解析/The processing instruction target matching "[xX][mM][lL]" is not allowed. The ...

  6. Activiti7 流程变量(理论)

    什么是流程变量 流程变量在Activiti中是一个非常重要的角色,流程运转有时需要靠流程变量,业务系统和Activiti结合时少不了流程变量,流程变量就是Activiti在管理工作流时根据管理需要而设 ...

  7. Ajxax技术-1

    1.什么是Ajax Ajax: Asynchronous javascript and xml (异步javascript和xml). ==Ajax并不是一种新技术,而是已有技术的集合.JavaScr ...

  8. 在MyBatis中采用模糊查询变量的引用标志应当是$而不是#

    具体如下例: @Select("select count(*) from hy_stock where name like '%${keyword}%' or code like '%${k ...

  9. 使用Flashback救回被误drop掉的表

    如果不慎把表drop掉了,并非一定要跑路,也许下面的文字能打救你. 比如现在有个testtb表,里面有一百万数据: SQL> select count(*) from testtb; COUNT ...

  10. 对韩峰著《SQL优化最佳实践》P7 案例的质疑

    事先申明下,我的DB环境是Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 - 64bit Production,如果与作者环境不同而 ...