ac自动机,用于解决而AC自动机解决的是长文本的多模板匹配问题。而KMP算法专门解决长文本的单模板匹配问题,字典树专门解决单个单词(短文本)多模板匹配问题。所以这里实际上就是KMP+字典树。
首先对于多个短单词,最好的存储办法——字典树。(这里是没有malloc()、free()的模板,几乎一样,只是用结构体的构造、析构函数取代了)。
然后KMP的话,肯定设计到一个next【】数组的问题了,在这里称之为失败指针,在结构体中新增 Node *fail指针来表示。求法在代码中体现。
最后getKMP()函数,就是相应的KMP部分了。
静态模板(改饶齐版,last数组优化版)
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int MAXNode=11000;
const int SIZE=26;
struct AC_Automaton
{
int ch[MAXNode][SIZE];
int val[MAXNode]; // 代表单词节点
int fail[MAXNode]; // fail数组
int last[MAXNode]; // last数组 —— fail的优化!!!
// last[i]=j :j节点表示的单词是i节点单词的后缀,且j节点是单词节点
int sz;//节点个数
void init()
{
sz=1;
memset(ch[0], 0, sizeof(ch[0]));
val[0]=0;
}
//insert负责构造ch与val数组
//插入字符串,v必须非0表示一个单词节点
void insert(char *s, int v)
{
int len=strlen(s);
int u=0;
for(int i=0; i<len; i++)
{
int id=s[i]-'a';
if(ch[u][id]==0)
{
ch[u][id]=sz;
memset(ch[sz], 0, sizeof(ch[sz]));
val[sz++]=0;
}
u=ch[u][id];
}
val[u]=v;
}
//build_AC_Automaton函数负责构造fail和last数组
void build_AC_Automaton()
{
int q[MAXNode];
int head=0, tail=0;
last[0]=fail[0]=0;
for(int i=0; i<SIZE; i++)//初始化root的儿子
{
int u=ch[0][i];
if(u)
{
fail[u]=last[u]=0;//
q[tail++]=u;
}
}
while(head<tail)// 按BFS顺序计算fail
{
int now=q[head++];
for(int i=0; i<SIZE; i++)
{
int u=ch[now][i];//儿子u
if(u==0)//该儿子不存在,不考虑
{
//ch[now][i]=ch[fail[now]][i];//连全部的边,Trie图
continue;
}
q[tail++]=u;//入队
int v=fail[now];
while(v && ch[v][i]==0)//一直找失败指针
v=fail[v];
fail[u]=ch[v][i];//求得 fail【u】
if(val[fail[u]]) //求得 last【u】
last[u]=fail[u];
else//如果 点fail[u] 不是单词结束点
last[u]=last[fail[u]];
}
}
}
//递归打印与结点i后缀相同的前缀节点编号
//进入函数条件:val[i]>0
void print(int p)
{
if(p)
{
print(last[p]);
}
}
// 在s中找出 出现了哪几个模板单词
void search(char *str)
{
int len=strlen(str);
int p=0;
for(int i=0; i<len; i++)
{
int id=str[i]-'a'; //第一步:更新p
while(p!=0 && ch[p][id]==0)
//跳出条件:①next【id】!=0, ②p=0
{
p=fail[p];
}
p=ch[p][id];
if(val[p]) //第二步:利用p
print(p);
else if(last[p])
print(last[p]);
}
}
}ac;
int main()
{
ac.init();
return 0;
}
动态模板(自改,并没有本质不同,更顺手了):
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
const int MAXN=1000010;
const int MAXM=51;
const int SIZE=26; //26个小写字母
int sz;
struct Node
{
Node *fail; //失败指针
Node *next[SIZE]; //儿子结点个数
int count; //单词个数
//int num; //节点编号 随sz++
Node()
{
fail=NULL;
count=0;
//num=sz++;
memset(next, 0, sizeof(next));
}
~Node()
{
delete next;
}
}*root;
Node *q[MAXN/2];
void Insert(Node *root, char *s)
{
int len=strlen(s);
Node *p=root;
for(int i=0; i<len; i++)
{
int id=s[i]-'a';
if(p->next[id]==NULL)
p->next[id]=new Node();//调用构造函数
p=p->next[id];
}
p->count++; //在单词的最后一个结点count + 1,代表一个单词
}
void build_AC_Automaton(Node *root)
{
root->fail=NULL;
int head=0, tail=0;
for(int i=0; i<SIZE; i++)//先初始化root的儿子
{
if(root->next[i]!=NULL)//儿子存在
{
root->next[i]->fail=root;//失败指针为root
q[tail++]=root->next[i];
}
}
while(head<tail)
{
Node *temp=q[head++];
for(int i=0; i<SIZE; i++)
{
if(temp->next[i]!=NULL)//儿子存在
{
Node *p=temp->fail;//
while(p!=NULL)
{
if(p->next[i]!=NULL)//如果失败指针有i儿子,
{
temp->next[i]->fail=p->next[i];//求得当前点i儿子的失败指针,
break; // 并退出
}
p=p->fail;//否则,更新失败指针
}
if(p==NULL)
temp->next[i]->fail=root;
q[tail++]=temp->next[i];
//cout<<temp->next[i]->num<<" fa "<<temp->next[i]->fail->num<<endl;
/* //错误代码!必须要用上面while循环一直找才行
if(p->next[i]!=NULL)
{
temp->next[i]->fail=p->next[i];
}
else if(p->next[i]==NULL)
{
temp->next[i]->fail=root;
}
if(p==NULL)
temp->next[i]->fail=root;
*/
}
}
}
}
/* //对应饶齐静态模板 print()函数,与下面while()互换 (利用p)
int print(Node *temp)
{
if(temp!=root && temp->count)
{
printf("%d\n", temp->count);
print(temp->fail);
}
}
*/
int Search(Node *root, char *str)
{
int cnt=0;
int len=strlen(str);
Node *p=root;//从根节点开始
for(int i=0; i<len; i++)
{
int id=str[i]-'a'; //---------------第一步:更新p
while(p->next[id]==NULL && p!=root)//更新p
//跳出条件:①next【i】!=NULL, ②p=root
{
p=p->fail;//p=p的失败指针
}
p=p->next[id];//next【i】儿子
if(p==NULL)//该儿子不存在,p=root
p=root;
Node *temp=p; //---------------第二步:利用p
while(temp!=root && temp->count!=-1)
//跳出条件:①temp=root, ②coumt=-1(访问过)
{
cnt+=temp->count;
temp->count=-1;
temp=temp->fail;
}
//cout<<"i= "<<i<<" cnt="<<cnt<<endl;
}
return cnt;
}
int main()
{
int N, T;
char keyword[MAXM]; //单词
char str[MAXN]; //模式串 文章
scanf("%d", &T);
while(T--)
{
scanf("%d", &N);
getchar();
//sz=0;
root=new Node();
while(N--)
{
scanf("%s", keyword);
Insert(root, keyword);
}
build_AC_Automaton(root);
scanf("%s", str);
printf("%d\n", Search(root, str));
}
return 0;
}
模板(初始):
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
const int MAXN=1000010; //模式串的最大长度MAXN - 1
const int MAXM=51; //单词最大长度为MAXM - 1
const int SIZE=26; //26个小写字母
struct Node
{
Node *fail; //失败指针
Node *next[SIZE]; //儿子结点个数
int count; //单词个数
Node()
{
fail=NULL;
count=0;
memset(next, 0, sizeof(next));
}
~Node()
{
delete next;
}
}*q[MAXN/2];
void Insert(Node *root, char *s)
{
int len=strlen(s);
Node *p=root;
for(int i=0; i<len; i++)
{
int id=s[i]-'a';
if(p->next[id]==NULL)
p->next[id]=new Node();//调用构造函数
p=p->next[id];
}
p->count++; //在单词的最后一个结点count + 1,代表一个单词
}
void build_AC_Automaton(Node *root)
{
root->fail=NULL;
int head, tail;
head=tail=0;
q[tail++]=root;//根节点入队
while(head<tail)
{
Node *temp=q[head++];//队首元素出队
for(int i=0; i<SIZE; i++)//temp的每个儿子 i就是当前儿子的字母
{
if(temp->next[i]!=NULL)//如果这个儿子存在的话
{
if(temp == root)//temp为根节点 初始化
{
temp->next[i]->fail=root;
}
else//非根
{
Node *p=temp->fail;//temp的失败指针
while(p!=NULL)
{
if(p->next[i]!=NULL)//如果失败指针的next【i】非空
{
temp->next[i]->fail=p->next[i];//更新儿子的失败指针,
break;//并跳出
}
p=p->fail;//沿着 失败指针 向上找
}
if(p==NULL)
temp->next[i]->fail=root;
}
q[tail++]=temp->next[i];//该儿子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 id=str[i]-'a';
while(p->next[id]==NULL && p!=root)
//跳出条件:①next【i】!=NULL, ②p=root
{
p=p->fail;//p=p的失败指针
}
p=p->next[id];//next【i】儿子
//p = (p == NULL) ? root : p;
if(p==NULL)//该儿子不存在,p=root
p=root;
Node *temp=p;
while(temp!=root && temp->count!=-1)
//跳出条件:①temp=root, ②coumt=-1(访问过)
{
cnt+=temp->count;
temp->count=-1;
temp=temp->fail;
}
//cout<<"i= "<<i<<" cnt="<<cnt<<endl;
}
return cnt;
}
int main()
{
int N, T;
Node *root;
char keyword[MAXM]; //单词
char str[MAXN]; //模式串 文章
scanf("%d", &T);
while(T--)
{
scanf("%d", &N);
getchar();
root=new Node();
while(N--)
{
scanf("%s", keyword);
Insert(root, keyword);
}
build_AC_Automaton(root);
scanf("%s", str);
printf("%d\n", query(root, str));
}
return 0;
}