AC自动机学习笔记

先简单复习一下学习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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Python AC自动机是一个用于字符串匹配的算法,它可以高效地在一段文本中查找多个预定义的模式。它的实现可以使用多种库,其中包括ac自动机python和ahocorasick-python。 ac自动机python是一个对标准的ac自动机算法进行了完善和优化的实现,适用于主流的Python发行版,包括Python2和Python3。它提供了更准确的结果,并且可以通过pip进行安装,具体的安装方法可以参考官方文档或者使用pip install命令进行安装。 ahocorasick-python是另一个实现AC自动机的库,它也可以用于Python2和Python3。你可以通过官方网站或者GitHub源码获取更多关于该库的信息和安装指南。 对于AC自动机的使用,一个常见的例子是在一段包含m个字符的文章中查找n个单词出现的次数。要了解AC自动机,需要有关于模式树(字典树)Trie和KMP模式匹配算法的基础知识。AC自动机算法包括三个步骤:构造一棵Trie树,构造失败指针和模式匹配过程。在构造好AC自动机后,可以使用它来快速地在文本中查找预定义的模式,并统计它们的出现次数。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [ahocorasick-python:AC自动机python的实现,并进行了优化。 主要修复了 查询不准确的问题](https://download.csdn.net/download/weixin_42122986/18825869)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [Python实现多模匹配——AC自动机](https://blog.csdn.net/zichen_ziqi/article/details/104246446)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值