看下面这个例子:给定5个单词:say she shr he her,然后给定一个字符串yasherhs。问一共有多少单词在这个字符串中出现过。我们先规定一下AC自动机所需要的一些数据结构,方便接下去的编程。
const int kind = 26;
struct node{
node *fail; //失败指针
node *next[kind]; //Tire每个节点的个子节点(最多个字母)
int count; //是否为该单词的最后一个节点
node(){ //构造函数初始化
fail=NULL;
count=0;
memset(next,NULL,sizeof(next));
}
}*q[500001]; //队列,方便用于bfs构造失败指针
char keyword[51]; //输入的单词
char str[1000001]; //模式串
int head,tail; //队列的头尾指针
void insert(char *str,node *root){ node *p=root; int i=0,index; while(str[i]){ index=str[i]-'a'; if(p->next[index]==NULL) p->next[index]=new node(); p=p->next[index]; i++; } p->count++; //在单词的最后一个节点count+1,代表一个单词 }
void build_ac_automation(node *root){ int i; root->fail=NULL; q[head++]=root; while(head!=tail){ node *temp=q[tail++]; //出队节点 node *p=NULL; for(i=0;i<26;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; } q[head++]=temp->next[i]; } } } }
最后,我们便可以在AC自动机上查找模式串中出现过哪些单词了。匹配过程分两种情况:(1)当前字符匹配,表示从当前节点沿着树边有一条路径可以到达目标字符,此时只需沿该路径走向下一个节点继续匹配即可,目标字符串指针移向下个字符继续匹配;(2)当前字符不匹配,则去当前节点失败指针所指向的字符继续匹配,匹配过程随着指针指向root结束。重复这2个过程中的任意一个,直到模式串走到结尾为止。
int query(node *root){ int i=0,cnt=0,index,len=strlen(str); node *p=root; while(str[i]){ index=str[i]-'a'; while(p->next[index]==NULL && p!=root) p=p->fail; p=p->next[index]; p=(p==NULL)?root:p; node *temp=p; while(temp!=root && temp->count!=-1){ cnt+=temp->count; temp->count=-1; temp=temp->fail; } i++; } return cnt; }对照图-2,看一下模式匹配这个详细的流程,其中模式串为yasherhs。对于i=0,1。Trie中没有对应的路径,故不做任何操作;i=2,3,4时,指针p走到左下节点e。因为节点e的count信息为1,所以cnt+1,并且讲节点e的count值设置为-1,表示改单词已经出现过了,防止重复计数,最后temp指向e节点的失败指针所指向的节点继续查找,以此类推,最后temp指向root,退出while循环,这个过程中count增加了2。表示找到了2个单词she和he。当i=5时,程序进入第5行,p指向其失败指针的节点,也就是右边那个e节点,随后在第6行指向r节点,r节点的count值为1,从而count+1,循环直到temp指向root为止。最后i=6,7时,找不到任何匹配,匹配过程结束。
15shehesayshrheryasherhs
3
#include <iostream> using namespace std; const int kind = 26; struct node{ node *fail; //失败指针 node *next[kind]; //Tire每个节点的26个子节点(最多26个字母) int count; //是否为该单词的最后一个节点 node(){ //构造函数初始化 fail=NULL; count=0; memset(next,NULL,sizeof(next)); } }*q[500001]; //队列,方便用于bfs构造失败指针 char keyword[51]; //输入的单词 char str[1000001]; //模式串 int head,tail; //队列的头尾指针 void insert(char *str,node *root){ node *p=root; int i=0,index; while(str[i]){ index=str[i]-'a'; if(p->next[index]==NULL) p->next[index]=new node(); p=p->next[index]; i++; } p->count++; } void build_ac_automation(node *root){ int i; root->fail=NULL; q[head++]=root; while(head!=tail){ node *temp=q[tail++]; node *p=NULL; for(i=0;i<26;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; } q[head++]=temp->next[i]; } } } } int query(node *root){ int i=0,cnt=0,index,len=strlen(str); node *p=root; while(str[i]){ index=str[i]-'a'; while(p->next[index]==NULL && p!=root) p=p->fail; p=p->next[index]; p=(p==NULL)?root:p; node *temp=p; while(temp!=root && temp->count!=-1){ cnt+=temp->count; temp->count=-1; temp=temp->fail; } i++; } return cnt; } int main(){ int n,t; scanf("%d",&t); while(t--){ head=tail=0; node *root=new node(); scanf("%d",&n); getchar(); while(n--){ gets(keyword); insert(keyword,root); } build_ac_automation(root); scanf("%s",str); printf("%d\n",query(root)); } return 0; }