Aho-Corasick自动机浅析

"AC自动机不是随便yy三分钟就搞定的算法么?"某犇如是说。蒟蒻MX默默流下了眼泪……html

因为各类机缘巧合和本人的智力因素,我在离开OI一年多后,终于搞清楚了AC自动机(Aho-Chorasick string match algorithm)。网络上介绍AC自动机的算法可能是借助KMP算法(Knuth-Morris-Pratt algorithm)中的失配数组来写,但明明AC自动机是先于KMP的,所以我决定彻底扔掉和KMP相关的东西,写一篇像我这样理解力比较低下的同窗也能看的懂的AC自动机算法讲解。

在讲述AC自动机以前,先简单讲讲自动机是什么。自动机是计算理论的一个概念,实际上是一张“图”,每一个点是一个“状态”,而边则是状态之间的转移,根据条件能指导从一个状态走向另外一个状态。不少字符串匹配算法都是基于自动机模型的,好比被普遍使用的正则表达式。node

AC自动机算法算是比较简单直观的字符串匹配自动机,它其实就是在一颗Trie树上建一些失配指针,当失配时只要顺着失配指针走,就能避免一些重复的计算。好比对于字符串antibody和tide,若是第一个串匹配到第5个字符(b)失配了能够直接走入第二个串的第3个字符(d)进行匹配,由于前面的“ti”是公共的,若是能匹配到第一个串的第5个字符,那么前面两个确定是ti。正则表达式

因此AC自动机分为三部分:

1.建Trie树

2.在Trie树上创建失配指针,成为AC自动机

3.自动机上匹配字符串算法

首先咱们先建构AC自动机的数据结构。既然基础是Trie树,咱们就用树的结构描述它。本文程序均用C++编写。如下是Trie树节点的结构体:数组

struct node
{
    node *fail;                     //失配指针
    node *child[CHAR_SET_SIZE];     //儿子节点
    int point;                      //标识这是第几个模式串的停止节点
    node(){
        fail=NULL;
        for (int i=0;i<CHAR_SET_SIZE;++i) child[i]=NULL;
        point=-1;                   //非模式串停止节点,用-1表达
    }
};

而后就是Trie树的插入,其实也是很简单、很模板的:网络

void Insert(char *s,int num)
{
    node *p=Root;
    for (char *c=s;*c!='\0';++c)
    {
        int t=(*c)-'a';
        if (p->child[t]==NULL)
        {
            p->child[t]=new node;
        }
        p=p->child[t];
        if ((*(c+1))=='\0') p->point=num; 
    }
}

AC自动机的精髓在于失配指针!失配指针的构建方法是这样的:对于一个节点C,标识字符a,顺着C的父亲节点的失配指针走,走到第一个有儿子也是a的节点T那么C的失配指针就指向T的标识a的儿子节点。若是找不到这个节点,那么失配指针指向Root。在实际操做时,是用广搜来实现,由于这个建构过程要求父亲节点的失配指针已经建好,并且一层层都要建好。代码以下:数据结构

void BuildFailPoint()
{
    int Qh=0,Qt=1;
    Q[1]=Root;
    while (Qh<Qt)
    {
        node *now=Q[++Qh];
        for (int i=0;i<CHAR_SET_SIZE;++i)
        {
            if (now->child[i]!=NULL)
            {
                if (now==Root) now->child[i]->fail=Root;
                else
                {
                    node *p=now->fail;
                    while (p!=NULL)
                    {
                        if (p->child[i]!=NULL)
                        {
                            now->child[i]->fail=p->child[i];
                            break;
                        }
                        p=p->fail;
                    }
                    if (p==NULL) now->child[i]->fail=Root;
                }
                Q[++Qt]=now->child[i];
            }
        }
    }
}

这样,AC自动机就建构完成了!如今对查询串进行匹配。匹配过程当中,须要一个p指针指向上一步成功匹配的节点。若是当前字符c失配,则p要沿着本身的失配指针走,直到新的p有一个儿子标识c,若是走不到,嘿嘿,已经回到根了。由于有可能会同时匹配多个串,因此须要扫一遍全部能够匹配的串。代码以下:ide

vector <pair <int,int> > Query()
{
    vector <pair <int,int> > Ret;             //查询返回值是第几个模式串在什么位置成功匹配
    int Len=strlen(QueryString);
    node *p=Root;
    for (int i=0;i!=Len;++i)
    {
        int index=QueryString[i]-'a';
        while (p->child[index]==NULL && p!=Root) p=p->fail;
        if (p->child[index]==NULL) continue;
        p=p->child[index];
        node *t=p;
        while (t!=Root)                      //扫全部能够匹配的串
        {
            if (t->point!=-1) Ret.push_back(make_pair(t->point,i));  
            t=t->fail;                       
        }
    }
    return Ret;
}

AC自动机核心的部分就这么多,我写了一个完整的AC自动机模板程序放在最后,仅供参考,如需使用该程序,请自便,无需告知我。ui

本文参考了:

* 《AC自动机算法详解》 极限定理

* 《Aho–Corasick string matching algorithm》 Wikipedia

* 《正则指引》,余晟著,电子工业出版社指针

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <string>
#include <vector>
#include <utility>
using std::string;
using std::vector;
using std::pair;
using std::make_pair;
#define CHAR_SET_SIZE 26
#define PATTERN_SIZE 300
#define QUERY_SIZE 3000
#define QSIZE 300000
struct node
{
    node *fail;
    node *child[CHAR_SET_SIZE];
    int point;
    node(){
        fail=NULL;
        for (int i=0;i<CHAR_SET_SIZE;++i) child[i]=NULL;
        point=-1;
    }
};
node *Q[QSIZE];
node *Root;
vector <string> Pattern_Collection;
void Init()
{
    Root=new node;
}
void Insert(char *s,int num)
{
    node *p=Root;
    for (char *c=s;*c!='\0';++c)
    {
        int t=(*c)-'a';
        if (p->child[t]==NULL)
        {
            p->child[t]=new node;
        }
        p=p->child[t];
        if ((*(c+1))=='\0') p->point=num; 
    }
}
void InputPattern()
{
    printf("Input number of patterns:");
    fflush(stdout);
    int N;
    scanf("%d",&N);
    char s[PATTERN_SIZE];
    for (int i=1;i<=N;++i)
    {
        scanf("%s",s);
        Pattern_Collection.push_back(s);
        Insert(s,Pattern_Collection.size()-1);
    }
}
void BuildFailPoint()
{
    int Qh=0,Qt=1;
    Q[1]=Root;
    while (Qh<Qt)
    {
        node *now=Q[++Qh];
        for (int i=0;i<CHAR_SET_SIZE;++i)
        {
            if (now->child[i]!=NULL)
            {
                if (now==Root) now->child[i]->fail=Root;
                else
                {
                    node *p=now->fail;
                    while (p!=NULL)
                    {
                        if (p->child[i]!=NULL)
                        {
                            now->child[i]->fail=p->child[i];
                            break;
                        }
                        p=p->fail;
                    }
                    if (p==NULL) now->child[i]->fail=Root;
                }
                Q[++Qt]=now->child[i];
            }
        }
    }
}
char QueryString[QUERY_SIZE];
vector <pair <int,int> > Query()
{
    vector <pair <int,int> > Ret;
    int Len=strlen(QueryString);
    node *p=Root;
    for (int i=0;i!=Len;++i)
    {
        int index=QueryString[i]-'a';
        while (p->child[index]==NULL && p!=Root) p=p->fail;
        if (p->child[index]==NULL) continue;
        p=p->child[index];
        node *t=p;
        while (t!=Root)
        {
            if (t->point!=-1) Ret.push_back(make_pair(t->point,i));
            t=t->fail;
        }
    }
    return Ret;
}
void InputQuery()
{
    printf("Input the query string:\n");
    scanf("%s",QueryString);
    vector < pair <int,int> > QueryAns=Query();
    for (int i=0;i!=QueryAns.size();++i)
    {
        printf("Found pattern \"%s\" at %d\n",
               Pattern_Collection[QueryAns[i].first].c_str(),
               QueryAns[i].second-Pattern_Collection[QueryAns[i].first].size()+1); 
    }
}
int main()
{
    Init();
    InputPattern();
    BuildFailPoint();
    InputQuery();
    return 0;
}
相关文章
相关标签/搜索