AC自动机

AC自动机(Aho-Corasick\ automaton),可以解决多模板串匹配的问题。可以理解为可以一次性匹配很多串的KMP。在KMP中,有一个失配函数next,在AC自动机中,也有一个类似的失配函数fail。类似于KMP的转移,每当AC自动机失配时,也会相应地转到当前节点的fail

Trie

为了一次性高效地匹配字符串,我们需要用一个数据结构来维护所有的模板串。这个数据结构就叫Trie。可以把Trie叫做字典树,所以Trie是一棵树。比如下面这个

Trie

这个就是一棵包含ab,abc,bd,dda四个串的Trie。通过观察,我们可以发现

  1. 根节点不包含字符

  2. 从根节点到某个节点简单路径上的字母即是这个节点代表的串

  3. 串的终点有标记

每次匹配,我们从Trie的根节点出发,尝试用文本串去匹配Trie树,由于Trie树将一些字符串压缩到了一起,因此时间复杂度会大大降低。但注意,Trie树是一种用空间换时间的做法

部分代码:

void init() {memset(trie[0],0,sizeof(trie[0])),ncnt=0;}
void insert(char s[],int len,int n) {//插入新串
    int now=0,c;
    for (int i=1;i<=len;i++) {//查找路径(没有则添加)
        c=idx(s[i]);
        if (trie[now][c]==0)
            now=trie[now][c]=++ncnt,memset(trie[now],0,sizeof(trie[now]));
        else now=trie[now][c];
    }
    val[now]=n;//标记终点
}

Trie树的空间复杂度是O(nmt)n是串的总数,m是最长串的长度,t是节点字母的种类

AC自动机实现

我们引入一个新的数组last,代表与当前节点有最长后缀的串在Trie上的编号。很明显,假如当前串匹配成功了,那么它的last也一定可以匹配成功(是它的后缀啊)

last的一个重要性质: p的失败指针指向节点k,则它应该满足这样的性质:从rootk的字符串完全匹配于从p往上相同长度的后缀

注意lastfail的区别:last是指当前节点的某个出现在模板串之中的后缀,而fail则不要求出现在模板串中

如果当前串匹配失败,那么就一直跳fail。我们这样考虑:

假设s[1,i]已经匹配成功了,第i个字符对应Trie上的u号节点,但是s[i+1]匹配失败了,很明显,我们可以从u节点跳到它的fail。因为s[1,i]已经匹配成功了,那么s[1,i]的某个后缀(对应fail)也一定可以匹配成功

int c=idx(s[i]);
while (now&&!trie[now][c]) now=fail[now];

举个例子

我们考虑这样一个自动机

AC自动机

这是一棵倒着的Trie加上一些虚线箭头一个AC自动机的状态图,绿色的点代表有标记。至于点上的数,可以暂时不管。

实线代表Trie上的边,虚线代表fail指向的节点

观察91这个点,它表示的串是SHE,它的fail指针指向76号点,这个点表示的串是HE,可以发现,它是SHE的一个后缀。

我们考虑在这样一棵Trie上匹配串SHERS

1.当前位于根节点,对应字符S,因此走85号点,匹配成功
2.当前位于85号点,对应字符H,因此走90号点,匹配成功
3.当前位于90号点,对应字符E,因此走91号点,匹配成功
4.91号点被标记过,匹配数+1
5.当前位于91号点,对应字符R,这个点没有字符为R的点,匹配失败,跳到91号点的fail指针76号点
6.当前位于76号点,对应字符R,因此走160号点,匹配成功
7.当前位于160号点,对应字符S,因此走86号点,匹配成功
8.至此,串SHERS已匹配完成

代码实现如下

void query(char s[],int len) {
    int now=0;
    for (int i=1;i<=len;i++) {
        int c=idx(s[i]);
        while (now&&!trie[now][c]) now=fail[now];//匹配失败,则一直跳fail指针
        now=trie[now][c];
        if (val[now]) cnt[val[now]]++;//匹配成功,且当前节点是某个模板串的末尾
        int las=last[now];
        while (val[las]) val[las]=0,cnt[val[las]]++,las=last[las];//当前节点匹配成功,它的last也一定匹配成功
    }
}

接下来是AC自动机的关键部分,即预处理fail指针与last指针

由于一个节点的fail的深度一定小于这个点的深度,所以我们考虑用bfs实现

考虑从一个已经求出faillast的节点now转移到他的后继节点son

如果nowfail指针有与son相同的后继节点,那么直接转移到那个后继节点。否则一直跳fail,直到根节点。

for (int i=0;i<128;i++) 
    if (trie[now][i]) {
        int f=fail[now];
        while (f&&!trie[f][i]) f=fail[f];
        int son=trie[now][i];
        fail[son]=trie[f][i];
    }

如果now节点所指向的fail的这个儿子已经被标记过,即是某个模板串的结尾,就直接将其赋到son,否则一直跳last

last[son]=val[fail[son]]?fail[son]:last[fail[son]];

bfs实现如下

void bfs() {
    queue<int> q;
    while (!q.empty()) q.pop();
    fail[0]=last[0]=0;
    for (int i=0;i<26;i++) {
        int now=trie[0][i];
        if (now) fail[now]=last[now]=0,q.push(now);
    }
    while (!q.empty()) {
        int now=q.front();q.pop();
        for (int i=0;i<26;i++) 
            if (trie[now][i]) {
                int f=fail[now];
                while (f&&!trie[f][i]) f=fail[f];
                int son=trie[now][i];
                fail[son]=trie[f][i],last[son]=val[fail[son]]?fail[son]:last[fail[son]];
                q.push(son);
            }
    }
}

最后是AC自动机模板代码

int ncnt=0,trie[100010][26],val[100010];
int idx(char c) {return c-'a';}
void init() {memset(trie[0],0,sizeof(trie[0])),ncnt=0;}
void insert(char s[],int len,int n) {
    int now=0,c;
    for (int i=1;i<=len;i++) {
        c=idx(s[i]);
        if (trie[now][c]==0)
            now=trie[now][c]=++ncnt,memset(trie[now],0,sizeof(trie[now]));
        else now=trie[now][c];
    }
    val[now]=n;//val里存的是串的编号
}
int fail[100010],last[100010];
void bfs() {
    queue<int> q;
    while (!q.empty()) q.pop();
    fail[0]=last[0]=0;
    for (int i=0;i<26;i++) {
        int now=trie[0][i];
        if (now) fail[now]=last[now]=0,q.push(now);
    }
    while (!q.empty()) {
        int now=q.front();q.pop();
        for (int i=0;i<26;i++) 
            if (trie[now][i]) {
                int f=fail[now];
                while (f&&!trie[f][i]) f=fail[f];
                int son=trie[now][i];
                fail[son]=trie[f][i],last[son]=val[fail[son]]?fail[son]:last[fail[son]];
                q.push(son);
            }
    }
}
int cnt[510];
void query(char s[],int len) {
    int now=0;
    for (int i=1;i<=len;i++) {
        int c=idx(s[i]);
        while (now&&!trie[now][c]) now=fail[now];
        now=trie[now][c];
        if (val[now]) cnt[val[now]]++;
        int las=last[now];
        while (val[las]) val[las]=0,cnt[val[las]]++,las=last[las];
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,524评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,869评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,813评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,210评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,085评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,117评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,533评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,219评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,487评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,582评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,362评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,218评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,589评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,899评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,176评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,503评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,707评论 2 335

推荐阅读更多精彩内容

  • 文不达意,口齿不清,思想混乱,令人喷饭。(估计只有我自己才能看懂我在说什么)简书没有mathjax公式没法愉快显示...
    njzwj阅读 1,164评论 1 2
  • 预备知识 Trie(字典树)KMP字符串匹配算法 AC自动机求解问题的类型 一句话概括就是:多模匹配。KMP求解的...
    ZJL_OIJR阅读 988评论 0 1
  • 类似于week2,3;然后这一节题目说是Trie图,其实用AC自动机更容易搜出来结果。对于一个M<=10^6的字符...
    vaisy阅读 198评论 0 0
  • 可重叠与不可重叠匹配比如模式串为aba,字符串为abababab,若可重叠匹配,那么aba出现的次数为三次;若为不...
    Gitfan阅读 617评论 0 0
  • fo0Old阅读 306评论 0 0