AC自动机

来源:互联网 发布:自然之名酵母水 知乎 编辑:程序博客网 时间:2024/06/10 16:12

AC自动机简介

首先简要介绍一下AC自动机:Aho-Corasick automation,该算法在1975年产生于贝尔实验室,是著名的多模匹配算法之一。一个常见的例子就是给出n个单词,再给出一段包含m个字符的文章,让你找出有多少个单词在文章里出现过。要搞懂AC自动机,先得有字典树Trie和KMP模式匹配算法的基础知识。KMP算法是单模式串的字符匹配算法,AC自动机是多模式串的字符匹配算法。

1.多模式匹配。
例如:求出模式集合{“nihao”,”hao”,”hs”,”hsr”}在给本”sdmfhsgnshejfgnihaofhsrnihao”中所有可能出现的位置。

2.Aho-Corasick算法 
  使用Aho-Corasick算法需要三步:
  1.建立模式的Trie。
  2.给Trie添加失败路径。
  3.根据AC自动机,搜索待处理的文本。
下面说明这三步:

2.1建立多模式集合的Trie树
Trie树也是一种自动机。对于多模式集合{“say”,”she”,”shr”,”he”,”her”},对应的Trie树如下:
这里写图片描述
2.2为多模式集合的Trie树添加失败路径,建立AC自动机
构造失败指针的过程概括起来就一句话:设这个节点上的字母为C,沿着他父亲的失败指针走,直到走到一个节点,他的儿子中也有字母为C的节点。然后把当前节点的失败指针指向那个字母也为C的儿子。如果一直走到了root都没找到,那就把失败指针指向root。
这里写图片描述

2.3根据AC自动机,搜索待处理的文本
构造好Trie和失败指针后,我们就可以对主串进行扫描了。这个过程和KMP算法很类似,但是也有一定的区别,主要是因为AC自动机处理的是多串模式,需要防止遗漏某个单词,所以引入temp指针。
匹配过程分两种情况:(1)当前字符匹配,表示从当前节点沿着树边有一条路径可以到达目标字符,此时只需沿该路径走向下一个节点继续匹配即可,目标字符串指针移向下个字符继续匹配;(2)当前字符不匹配,则去当前节点失败指针所指向的字符继续匹配,匹配过程随着指针指向root结束。重复这2个过程中的任意一个,直到模式串走到结尾为止。
对照上图,看一下模式匹配这个详细的流程,其中模式串为yasherhs。对于i=0,1。Trie中没有对应的路径,故不做任何操作;i=2,3,4时,指针p走到左下节点e。因为节点e的count信息为1,所以cnt+1,并且讲节点e的count值设置为0,表示改单词已经出现过了,防止重复计数,最后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时,找不到任何匹配,匹配过程结束。

AC自动机的源代码

1.数据结构

typedef struct Tnode{    int count;         //每个单词的最后一个字母的coount!=0代表有几个这样的单词    char ch;    struct Tnode* fail;  // 该节点的fail指针用于扫描    struct Tnode* next[26]; //记录该节点的下一个字母的节点    struct Tnode* parent;   //记录该节点的父节点 在构建fail指针时用到}TrilNode,*TrilTree;

2.对每个节点的fail域赋值。赋值采用BFS来进行。其中root的fail指针赋为NULL。root的parent指针也赋为root。其他没有找到匹配的节点时将其赋为root.

void buildFail(TrilTree root){    queue<TrilTree> Q;    TrilNode* t=NULL;    Q.push(root);    while(!Q.empty())    {        TrilNode* node=Q.front();        t=node->parent;        while(t->fail!=NULL)       //给node节点的fail赋值        {           if(t->fail->next[node->ch-'a']!=NULL)           {               node->fail=t->fail->next[node->ch-'a'];               break;           }           else            t=t->fail;        }        if(t->fail==NULL&&node!=root)            node->fail=root;        for(int i=0;i<26;i++)        {            if(node->next[i]!=NULL)                Q.push(node->next[i]);        }        Q.pop();    }}

3.对目标串进行扫描。

int searchAC(string s,TrilTree root){    int len=s.size();    int num=0;    TrilNode* q=NULL;    TrilNode* p=root;    for(int i=0;i<len;i++)    {        if(p->next[s[i]-'a']==NULL)        {            while(p->fail!=NULL)            {                p=p->fail;                if(p->next[s[i]-'a']!=NULL)                {                    p=p->next[s[i]-'a'];                    break;                }            }            if(p==root)            {                continue;            }        }        else        {            p=p->next[s[i]-'a'];        }        if(p->count!=0)        {            num+=p->count;            p->count=0;            q=p->fail;            while(q!=NULL)            {                if(q->count!=0)                {                    num+=q->count;                    q->count=0;                }                q=q->fail;            }        }    }    return num;}

最终代码只要在main函数里面分别根据函数的格式进行调用即可。完整代码如下:

#include <iostream>#include<string>#include<cstdlib>#include<queue>using namespace std;typedef struct Tnode{    int count;         //每个单词的最后一个字母的coount!=0代表有几个这样的单词    char ch;    struct Tnode* fail;  // 该节点的fail指针用于扫描    struct Tnode* next[26]; //记录该节点的下一个字母的节点    struct Tnode* parent;   //记录该节点的父节点 在构建fail指针时用到}TrilNode,*TrilTree;void buildTril(string s,TrilTree root){     TrilNode*p=root;     int len=s.size();     for(int i=0;i<len;i++)     {         if(p->next[s[i]-'a']==NULL)         {             TrilNode* node=(TrilNode*)malloc(sizeof(TrilNode));             for(int i=0;i<26;i++)                node->next[i]=NULL;             node->count=0;             node->next[s[i]-'a']=NULL;             node->parent=p;             node->ch=s[i];             p->next[s[i]-'a']=node;         }         p=p->next[s[i]-'a'];     }     p->count++;}void buildFail(TrilTree root){    queue<TrilTree> Q;    TrilNode* t=NULL;    Q.push(root);    while(!Q.empty())    {        TrilNode* node=Q.front();        t=node->parent;        while(t->fail!=NULL)       //给node节点的fail赋值        {           if(t->fail->next[node->ch-'a']!=NULL)           {               node->fail=t->fail->next[node->ch-'a'];               break;           }           else            t=t->fail;        }        if(t->fail==NULL&&node!=root)            node->fail=root;        for(int i=0;i<26;i++)        {            if(node->next[i]!=NULL)                Q.push(node->next[i]);        }        Q.pop();    }}int searchAC(string s,TrilTree root){    int len=s.size();    int num=0;    TrilNode* q=NULL;    TrilNode* p=root;    for(int i=0;i<len;i++)    {        if(p->next[s[i]-'a']==NULL)        {            while(p->fail!=NULL)            {                p=p->fail;                if(p->next[s[i]-'a']!=NULL)                {                    p=p->next[s[i]-'a'];                    break;                }            }            if(p==root)            {                continue;            }        }        else        {            p=p->next[s[i]-'a'];        }        if(p->count!=0)        {            num+=p->count;            p->count=0;            q=p->fail;            while(q!=NULL)            {                if(q->count!=0)                {                    num+=q->count;                    q->count=0;                }                q=q->fail;            }        }    }    return num;}int main(){    int m,n,Num;    string s,t;    TrilNode* node=NULL;    TrilTree root;    root=(TrilTree)malloc(sizeof(TrilNode));    root->count=0;    root->fail=NULL;    root->parent=root;    root->ch='#';    for(int i=0;i<26;i++)        root->next[i]=NULL;    cin>>m;    while(m--)    {        cin>>n;        for(int i=0;i<n;i++)        {            cin>>s;            buildTril(s,root);        }        cin>>t;        buildFail(root);        Num=searchAC(t,root);        cout<<Num;    }    return 0;}
0 0
原创粉丝点击