AC自动机是解决多模式串匹配算法,常见的例子就是给出n个单词,再给出一段包含m个字符的文章,让你找出有多少个单词在文章里出现过。
AC自动机一般用三步:
1.建立模式的Trie(字典树)
字典树模板:https://blog.csdn.net/baodream/article/details/80685799
2.给Trie添加失败路径(fail指针)
推荐这篇博客:https://blog.csdn.net/creatorx/article/details/71100840
概括起来就一句话:设这个节点上的字母为C,沿着他父亲的失败指针走,直到走到一个节点,他的儿子中也有字母 为C的节点。然后把当前节点的失败指针指向那个字母也为C的儿子。如果一直走到了root都没找到,那就把失败指针指向root。
使用广度优先搜索BFS,层次遍历节点来处理,每一个节点的失败路径。
3.根据AC自动机,搜索待处理的文本
数组模拟代码:
const int MAXN = 5e5+5;
#define son_num 26 //注意修改
struct Trie{
int tree[MAXN][son_num]; //26是讨论全小写字母情况,根据题意修改
int fail[MAXN]; //fail指针,匹配失败时返回位置
int cnt[MAXN]; //cnt数组表示以该节点结束的字符串数量
int root,tot; //root是根节点,tot标记节点序号
int newnode(){
for(int i=0;i<son_num;i++)
tree[tot][i] = -1;
cnt[tot++] = 0;
return tot-1; //返回当前节点编号
}
void init(){
tot = 0;
root = newnode();
}
int get_id(char c){ //返回儿子节点编号,注意修改
return c-'a';
}
void Insert(char *s){
int len = strlen(s);
int now = root;
for(int i=0;i<len;i++){
int id = get_id(s[i]);
if(tree[now][id]==-1) //无后继节点,新建节点
tree[now][id] = newnode();
now = tree[now][id];
}
cnt[now]++;
}
void build(){ //建立fail数组,构造失配指针
queue<int>q; //bfs寻找
fail[root] = root; //根节点的fail直接指向自己
for(int i=0;i<son_num;i++){
if(tree[root][i]==-1)
tree[root][i] = root;
else{ //根节点儿子的fail指针指向根节点
fail[tree[root][i]]=root;
q.push(tree[root][i]);
}
}
while(!q.empty()){
int now = q.front();
q.pop();
for(int i=0;i<son_num;i++){ //构造该节点的所有儿子fail指针
if(tree[now][i]==-1)
tree[now][i] = tree[fail[now]][i]; //该段的最后一个节点匹配后,跳到拥有最大公共后缀的fail节点继续匹配
else{
fail[tree[now][i]] = tree[fail[now]][i]; //当前节点的fail节点等于它前驱节点的fail节点的后继节点
q.push(tree[now][i]);
}
}
}
}
int query(char *s){
int len = strlen(s);
int now = root;
int ans = 0;
for(int i=0;i<len;i++){
int id = get_id(s[i]);
now = tree[now][id];
int tmp = now;
while(tmp != root){
ans+=cnt[tmp]; //加上以当前节点结尾的字符串数
cnt[tmp] = 0; //可防止计算重复的字符串
tmp = fail[tmp]; //每次找最大公共后缀对应的fail节点
}
}
return ans;
}
void debug(){
for(int i = 0;i < tot;i++){
printf("id = %3d,fail = %3d,cnt = %3d,chi = [",i,fail[i],cnt[i]);
for(int j = 0;j < son_num;j++)
printf("%2d",tree[i][j]);
printf("]\n");
}
}
}ac;
指针模拟代码:
#define son_num 26 //儿子节点数量,注意修改
struct node{
int terminal; //结束位置数量
node *fail;
node *Next[son_num];
node(){
fail=NULL;
terminal=0; //记录结束位置
for(int i=0;i<son_num;i++)
Next[i] = NULL;
}
};
int get_id(char c){ //这里注意修改
return c-'a';
}
void Insert(node *root,char *str){//x为该病毒的编号
node *p=root;
int len = strlen(str);
for(int i=0;i<len;i++){
int index=get_id(str[i]);
if(p->Next[index]==NULL)
p->Next[index]=new node();
p=p->Next[index];
}
p->terminal++;
}
//寻找失败指针
void build_fail(node *root){
queue <node *> que;
root->fail=NULL;
que.push(root);
while(!que.empty()){
node *temp=que.front();
que.pop();
node *p=NULL;
for(int i=0;i<son_num;i++){
if(temp->Next[i]!=NULL){
if(temp==root) temp->Next[i]->fail=root;
else{
p=temp->fail;
while(p!=NULL){
if(p->Next[i]!=NULL){
temp->Next[i]->fail=p->Next[i];
break;
}
p=p->fail;
}
if(p==NULL)
temp->Next[i]->fail=root;
}
que.push(temp->Next[i]);
}
}
}
}
//询问主串中含有多少个关键字
int query(node *root,char *str){
int cnt=0;
int len=strlen(str);
node *p=root;
for(int i=0;i<len;i++){
int index=get_id(str[i]);
while(p->Next[index]==NULL&&p!=root)
p=p->fail;
p=p->Next[index];
if(p==NULL) p=root;
node *temp=p;
while(temp!=root){
cnt+=temp->terminal;
temp->terminal = 0; //防止重复加字符串
temp=temp->fail;
}
}
return cnt;
}
//指针处理
void deal(node *now){
if(now == NULL)
return ;
for(int i=0;i<son_num;i++){
if(now->Next[i]!=NULL)
deal(now->Next[i]);
}
delete now;
}
//node *root = new node(); //建立根节点