ac自动机感觉就是kmp+trie树,是一种高效的多串匹配算法。
其关键就在于在trie树上加上一种fail指针,而fail相当于kmp中的next数组,表示当前节点匹配失败后,转移到继续匹配的节点。
例如这颗trie树
其c字符节点与d字符节点的fail指针分别是 两条红线。
知道fail指针的定义后,就是如何建立fail指针了。
建立fail指针的过程是bfs的过程,利用之前已经遍历过的父节点的fail指针建立当前节点的fail指针。
若父节点通过某一字符连接到其一个儿子节点,并且父亲节点fail指针指向的节点也有连向这一字符的儿子节点,则将儿子指针连向父亲节点的前缀节点的相应字符节点。
看起来有些绕。举个例子:
当bfs遍历到1节点时,1节点通过c连接3节点,这时1节点的fail指针指向root,root也通过c连接2节点,这时可以将3节点的fail指针指向2节点。
我们可以看到1节点没有通过z字符连接的节点 所以我们将它连向root节点通过z连向的节点 也就是root本身。
这样ac自动机就建立好了。
#include<stdio.h>
#include<string.h>
#include<queue>
#define maxlen 1000005
using namespace std;
struct Trie
{
int next[maxlen][30],fail[maxlen],end[maxlen],root,L;//next记录节点,在这里end指针代表以当前节点为字符串尾的字符串个数
int newnode()
{
for(int i=0;i<30;i++)
next[L][i]=-1;//节点连接的边初始化为-1
end[L]=0;
return L++;
}
void init()
{
L=0;
root=newnode();
}
void insert(char buf[])//trie树的建立
{
int l=strlen(buf);
int now=root;
for(int i=0;i<l;i++)
{
if(next[now][buf[i]-'a']==-1)next[now][buf[i]-'a']=newnode();
now=next[now][buf[i]-'a'];
}
end[now]++;
}
void build()//建立ac自动机
{
queue<int>que;
for(int i=0;i<30;i++)
{
if(next[root][i]==-1)next[root][i]=root;
else //若有连边则将节点加入队列 ,并将fail指针指向root
{
fail[next[root][i]]=root;
que.push(next[root][i]);
}
}
while(!que.empty())
{
int now=que.front();
que.pop();
for(int i=0;i<30;i++)
{
if(next[now][i]==-1) //若无连边,则将该边指向当前节点fail指针指向的相应字符连接的节点
next[now][i]=next[fail[now]][i];
else //若有连边,则将儿子节点的fail指针指向当前节点fail指针指向相应字符接的节点
{
fail[next[now][i]]=next[fail[now]][i];
que.push(next[now][i]); //加入队列继续遍历
}
}
}
}
int query(char buf[])
{
int l=strlen(buf);
int now=root;
int res=0;
for(int i=0;i<l;i++)
{
now=next[now][buf[i]-'a'];
int temp=now;
while(temp!=root)//根据题目要求改变形式
{
res+=end[temp];
end[temp]=0;
temp=fail[temp];
}
}
return res; //在这里返回的是匹配到的模式串的数量
}
}ac;
1862

被折叠的 条评论
为什么被折叠?



