美文网首页算法
AC自动机学习笔记

AC自动机学习笔记

作者: _NewMoon | 来源:发表于2020-09-29 19:20 被阅读0次

先简单复习一下学习AC自动机所需要的前缀知识。

前缀知识 1-Trie树

字典树,也称Trie树,前缀树,主要用于存储大量的字符串以及查询操作。
对于Trie树,一般有两个操作:

  • 1.insert操作,在Trie树中存储一个字符串
  • 2.query操作,在Trie树中查询一个字符串

举个例子,对于这样几个字符串,{abcde,abcgf,hello,her}我们看他们在Trie树中是如何存储的:

Trie.png

这里需要注意,字符是边,而不是节点,但都是一一对应的

代码

int son[N][26],cnt[N],idx;  //cnt数组记录以当前节点为结尾的字符串的数目,idx为节点编号
int n;

void insert(string& s){
    int n = s.length();
    int p = 0;
    for(int i = 0; i<n; i++){
        int u = s[i]-'a';
        if(!son[p][u]) son[p][u] = ++idx;
        p = son[p][u];
    }
    cnt[p] ++;
}

int query(string &s){
    int p = 0;
    int n = s.length();
    for(int i = 0; i<n; i++){
        int u = s[i] - 'a';
        if(!son[p][u]) return 0;
        p = son[p][u];
    }
    return cnt[p];
}

前缀知识2 - KMP算法

对于KMP算法的介绍网上有很多优秀的博客,这里就不再赘述了。。。

在学习了Trie树和KMP算法后,我们就可以学习AC自动机了。
这里放个B站的视频链接,我觉得讲得很清楚-->轻松掌握AC自动机

AC自动机(Aho-Corasick automaton)

了解:我们知道KMP算法是解决单模式串的匹配问题的,而AC自动机是用来解决多模式串匹配问题的。

对于多模式串的存储,我们就可以用到Trie树来存储,在匹配过程中,一旦发生失配,我们应该如何处理呢?在KMP算法中,我们对模式串构建了next数组,在失配时通过next数组来加速匹配过程,那在Trie树中,我们是不是也可以通过一个数组来维护前缀和后缀的某些信息呢?

fail数组

定义:对于Trie树中当前某个节点i,若fail[i]=j表示Trie树中到j为止的字符串是到i为止的字符串的最长后缀

一张图看懂fail数组:为了避免图片过乱,对于fail[i] = 0的点(即指向root的点)均未画出

fail

有了这个fail数组后,我们模拟一个简单的样例,比如当前主串是uhershef

ac自动机

大家可以自己模仿样例在纸上画一下过程,在理解了这个过程之后,其实fail数组也就不再那么神秘了,当我们在一个分支上走不下去了之后,我们不必头铁,可以换个方向继续往下走,而fail数组的作用就是这样,当发生失配时,我们可以通过预处理出的fail数组告诉我们其他分支的信息(即满足当前字符串最长后缀的节点),然后继续向后遍历即可。

如何求fail数组

在Trie树建好之后,我们如何给这棵树添加信息即如何预处理出fail数组,其实仔细观察第一幅图我们就可以看出来,我们可以利用前一层的信息来更新下一层的节点信息,那么我们可以用BFS来一层一层地扩展。
代码如下:

void build(){
    int hh = 0, tt = -1;
    for(int i = 0; i<26; i++){
        if(tr[0][i]) q[++tt] = tr[0][i];  //第一层的所有节点一定指向root
    }
    
    while(hh<=tt){
        int t = q[hh++];
        
        for(int i = 0; i<26; i++){
            int c = tr[t][i];
            //利用父节点的信息来更新自己的
            if(!c) tr[t][i] = tr[fail[t]][i];  //将树填充为图,这里可以通过测试样例测试一下
            else{
                fail[c] = tr[fail[t]][i];
                q[++tt] = c;
            }
        }
    }
}

强烈建议大家画个图自己模拟一遍,绝对比直接对照代码看有效!!

匹配

匹配的过程在第二张图里面已经提及到了,大致过程如下,从根节点开始匹配,如果匹配,继续跳到下一个节点,如果失配,则跳到fail指针,然后继续匹配。
对于Trie图,我的个人理解是,利用fail指针来补充trie树的信息,使其成为一个图,然后在匹配过程中,我们无需考虑fail指针,直接令j = tr[j][t],这里其实就包括了t+'a'这个字符如果不存在时的情况,如果不存在,其实就等价于j = tr[fail[j]][t]

代码

#include <bits/stdc++.h>

using namespace std;

const int N = 1e4+100, L = 55, M = 1e6+100;

char str[M],s[L];
int tr[N*L][26], cnt[N*L],idx;
int fail[N*L],q[N*L];
int t,n;

//字典树建树过程
void insert(char s[]){
    int p = 0;
    for(int i = 0; s[i]; i++){
        int t = s[i]-'a';
        if(!tr[p][t]) tr[p][t] = ++idx;
        p = tr[p][t];
    }
    cnt[p]++;
}

//宽搜预处理出fail数组以及trie子图
void build(){
    int hh = 0, tt = -1;
    for(int i = 0; i<26; i++){
        if(tr[0][i]) q[++tt] = tr[0][i];  //第一层直接入队
    }
    
    while(hh<=tt){
        int t = q[hh++];
        
        for(int i = 0; i<26; i++){
            int c = tr[t][i];
            if(!c) tr[t][i] = tr[fail[t]][i];  
            else{
                fail[c] = tr[fail[t]][i];  //利用上层信息更新现在的节点信息
                q[++tt] = c;
            }
        }
    }
}
int main()
{
    scanf("%d",&t);
    while(t--){
        scanf("%d",&n);
        
        memset(tr,0,sizeof tr);
        memset(fail,0,sizeof fail);
        memset(cnt,0,sizeof cnt);
        
        for(int i = 1; i<=n; i++){
            scanf("%s",s);
            insert(s);
        }
        
        build();
        
        scanf("%s",str);
        int ans = 0;
        for(int i = 0,j = 0; str[i]; i++){
            int t = str[i] - 'a'; //当前遍历到的字符
            j = tr[j][t];
            int p = j;
            while(p){
                ans += cnt[p];//更新答案
                cnt[p] = 0;  //统计出现的种类,所以清空标记
                p = fail[p];  //向上跳,不漏答案
            }
        }
        printf("%d\n",ans);
    }
    return 0;
}

相关文章

  • AC自动机 专题整理

    AC自动机学习记录 参考资料 字典树(讲解+模版)AC自动机算法AC自动机算法详解hdu 2222 ac自动机入门...

  • AC自动机学习笔记

    文不达意,口齿不清,思想混乱,令人喷饭。(估计只有我自己才能看懂我在说什么)简书没有mathjax公式没法愉快显示...

  • AC自动机学习笔记

    先简单复习一下学习AC自动机所需要的前缀知识。 前缀知识 1-Trie树 字典树,也称Trie树,前缀树,主要用于...

  • 回文树(附模板题URAL-1960)

    (最好事先学习过kmp,Trie,AC自动机)回文树,有效解决各类回文问题的超级666的树形结构 集AC自动机的f...

  • 自动AC机?不,是AC自动机

    今天我们来介绍一点进阶的知识——AC自动机。 AC自动机是啥 AC自动机是什么呢?是不是用了这个算法,不管什么题目...

  • 【AC自动机】AC自动机可以帮你自动AC吗

    参考博文:AC自动机算法详解 (转载) (原文作者:DarkRaven,原文的链接失效了)图片来源:AC自动机算...

  • AC自动机-去除敏感字符

    AC自动机 AC自动机:Aho-Corasick automation,该算法在1975年产生于贝尔实验室,是著名...

  • lucene中文分词

    IK中文分词 DoubleArrayTrie的AC自动机

  • 字符串算法小结

    hash kmp和ac自动机 后缀数组,后缀自动机,后缀树 扩展kmp manacher算法 回文自动机 可删改的...

  • AC 自动机

    AC自动机 AC自动机是一个经典的多模式串匹配算法,它可以实现对主串的一次扫描来匹配多个模式串的功能。实现AC自动...

网友评论

    本文标题:AC自动机学习笔记

    本文链接:https://www.haomeiwen.com/subject/tcujuktx.html