AC自动机 (多模式匹配)

2023-11-18

AC自动机

感谢博主:https://blog.csdn.net/bestsort/article/details/82947639

感谢博主 : https://fanfansann.blog.csdn.net/article/details/106160470

简介

KMP
Tire

要学AC自动机需要自备两个前置技能:KMP和trie树(其实个人感觉不会kmp也行,失配指针的概念并不难)

其中,KMP是用于一对一的字符串匹配,而trie虽然能用于多模式匹配,但是每次匹配失败都需要进行回溯,如果模式串很长的话会很浪费时间,所以AC自动机应运而生,如同Manacher一样,AC自动机利用某些操作阻止了模式串匹配阶段的回溯,将时间复杂度优化到了O ( n ) O(n)为文本串长度

建立AC自动机

建立一个AC自动机通常需要两个步骤:

  • 基础的TRIE结构:将所有的模式串构成一棵Trie。
  • KMP的思想:对Trie树上所有的结点构造失配指针。

然后就可以利用它进行多模式匹配了。

构造失配( fail )指针

在讲构造以前,先来对比一下这里的 fail 指针与KMP中的next指针:

共同点-两者同样是在失配的时候用于跳转的指针。
不同点-KMP要求的是最长相同真前后缀,而AC自动机只需要相同后缀即可。
因为KMP只对一个模式串做匹配,而AC自动机要对多个模式串做匹配。
有可能 fail 指针指向的结点对应着另一个模式串,两者前缀不同。
也就是说,AC自动机在对匹配串做逐位匹配时,同一位上可能匹配多个模式串。
因此 fail指针会在字典树上的结点来回穿梭,而不像KMP在线性结构上跳转。

下面介绍构建 fail指针的基础思想:(强调!基础思想!基础!)
构建 fail指针,可以参考KMP中构造next数组的思想。
我们利用部分已经求出 fail 指针的结点推导出当前结点的 fail 指针。具体我们用BFS实现:
考虑字典树中当前的节点u,u的父节点是p,p通过字符c的边指向u。
假设深度小于u的所有节点的 fail指针都已求得。那么p的 fail 指针显然也已求得。
我们跳转到p的 fail 指针指向的结点 fail[p] ;
如果结点 fail[p] 通过字母 c 连接到的子结点 w 存在:
则让u的fail指针指向这个结点 w( fail[u]=w )。
相当于在 p 和 fail[p] 后面加一个字符 c ,就构成了 fail[u] 。
如果 fail[p] 通过字母 c 连接到的子结点 w 不存在:
那么我们继续找到 fail[fail[p]] 指针指向的结点,重复上述判断过程,一直跳 fail 指针直到根节点。
如果真的没有,就令 fail[u]= 根节点。
如此即完成了 fail 指针的构建。

图解构造

  • 黄色结点表示当前的结点u,绿色结点表示已经BFS遍历完毕的结点,红/橙色的边表示 fail指针。
  • 2号节点的 fail 指针画错了, fail[2]=0

img

  • 我们重点分析结点6的 fail指针构建: AC_automation_6_9.png
  • 找到6的父节点5,5的 fail指针指向10,然而10结点没有字母’s’连出的边;
  • 所以跳到10的 fail 指针指向的结点0,发现0结点有字母’s’连出的边,指向7结点;
  • 所以 fail[6]=7

另外,在构建 fail指针的同时,我们也对TRIE中模式串的结尾构建 fail指针。这样在匹配到结尾后能自动跳转到下一个匹配项。具体见代码实现。

下面开始用图学习ac自动机吧(个人比较喜欢放图,能用一张图解决的绝不叨叨)
首先给定模式串"ash",“shex”,“bcd”,“sha”,然后我们根据模式串建立如下trie树:

img

然后我们再了解下一步:
ac自动机,就是在tire树的基础上,增加一个fail指针,如果当前点匹配失败,则将指针转移到fail指针指向的地方,这样就不用回溯,而可以路匹配下去了.(当前模式串后缀和fail指针指向的模式串部分前缀相同,如abce和bcd,我们找到c发现下一个要找的不是e,就跳到bcd中的c处,看看此处的下一个字符(d)是不是应该找的那一个)

一般,fail指针的构建都是用bfs实现的
首先每个模式串的首字母肯定是指向根节点的(一个字母你瞎指什么指,指了也是头字母有什么用嘛)

在这里插入图片描述

现在第一层bfs遍历完了,开始第二层
(根节点为第0层)第二层a的子节点为s,但是我们还是要从a-z遍历,如果不存在这个子节点我们就让他指向根节点(如下图红色的a)

在这里插入图片描述

当我们遍历到s的时候,由于存在s这个节点,我们就让他的fail指针指向他父亲节点(a)的fail指针指向的那个节点(根)的具有相同字母的子节点(第一层的s),也就是这样

在这里插入图片描述

按照相同规律构建第二层后,到了第三层的h点,还是按照上面的规则,我们找到h的父亲节点(s)fail指针指向的那个位置(第一层的s)然后指向它所指向的相同字母根->s->h的这个链的h节点,如下图

在这里插入图片描述

完全构造好后的树

在这里插入图片描述

然后匹配就很简单了,这里以ashe为例
我们先用ash匹配,到h了发现:诶这里ash是一个完整的模式串,好的ans++,然后找下一个e,可是ash后面没字母了啊,我们就跳到hfail指针指向的那个h继续找,还是没有?再跳,结果当前的h指向的是根节点,又从根节点找,然而还是没有找到e,程序END

过程如下图

模板


#include <queue>
#include <cstdlib>
#include <cmath>
#include <cstdio>
#include <string>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const int maxn =  2*1e6+9;

int trie[maxn][26]; //字典树
int cntword[maxn];  //记录该单词出现次数
int fail[maxn];     //失败时的回溯指针
int cnt = 0;

void insertWords(string s){
    int root = 0;
    for(int i=0;i<s.size();i++){
        int next = s[i] - 'a';
        if(!trie[root][next])
            trie[root][next] = ++cnt;
        root = trie[root][next];
    }
    cntword[root]++;      //当前节点单词数+1
}
void getFail(){
    queue <int>q;
    for(int i=0;i<26;i++){      //将第二层所有出现了的字母扔进队列
        if(trie[0][i]){
            fail[trie[0][i]] = 0;
            q.push(trie[0][i]);
        }
    }

//fail[now]    ->当前节点now的失败指针指向的地方
tire[now][i] -> 下一个字母为i+'a'的节点的下标为tire[now][i]
    while(!q.empty()){
        int now = q.front();
        q.pop();

        for(int i=0;i<26;i++){      //查询26个字母
            if(trie[now][i]){
                //如果有这个子节点为字母i+'a',则
//让这个节点的失败指针指向(((他父亲节点)的失败指针所指向的那个节点)的下一个节点)
                //有点绕,为了方便理解特意加了括号

                fail[trie[now][i]] = trie[fail[now]][i];
                q.push(trie[now][i]);
            }
            else//否则就让当前节点的这个子节点
                //指向当前节点fail指针的这个子节点
                trie[now][i] = trie[fail[now]][i];
        }
    }
}


int query(string s){
    int now = 0,ans = 0;
    for(int i=0;i<s.size();i++){    //遍历文本串
        now = trie[now][s[i]-'a'];  //从s[i]点开始寻找
        for(int j=now;j && cntword[j]!=-1;j=fail[j]){
            //一直向下寻找,直到匹配失败(失败指针指向根或者当前节点已找过).
            ans += cntword[j];
            cntword[j] = -1;    //将遍历国后的节点标记,防止重复计算
        }
    }
    return ans;
}

int main() {
    int n;
    string s;
    cin >> n;
    for(int i=0;i<n;i++){
        cin >> s ;
        insertWords(s);
    }
    fail[0] = 0;
    getFail();
    cin >> s ;
    cout << query(s) << endl;
    return 0;
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

AC自动机 (多模式匹配) 的相关文章

随机推荐

  • echarts首次加载中国地图空白

    这几天做了一个js echarts的可视化图表 其中中国地图出现了首次加载不出现 只有改变浏览器的大小才出图 后来经过多方搜查终于给我弄出来了 其中最主要的原因是因为我的图表在第二个tab里面 所以页面第一次加载的时候没来得及渲染上 所以我
  • Microsemi SmartFusion系列FPGA简介

    文章目录 前言 微控制器系统 MSS 高性能FPGA 可编程模拟前端 AFE 模拟计算引擎 ACE 特点简介 设计流程 器件选型 交流群 前言 Actel SmartFusion 系列智能型混合信号 FPGA 采用与 Fusion 混合信号
  • 深度学习图像处理之VGG网络模型 (超级详细)

    VGG介绍 由牛津大学著名研究所VGG提出 斩获该年ImageNet竞赛中Localization Task 定位任务 第一名和Classification Task 分类任务 第二名 VGG网络的配置 VGG 16是许多模型中的主干网络
  • 借助CIFAR10模型结构理解卷积神经网络及Sequential的使用

    CIFAR10模型搭建 CIFAR10模型结构 0 input 3 32x32 3通道32x32的图片 gt 特征图 Feature maps 32 32x32即经过32个3 5x5的卷积层 输出尺寸没有变化 有x个特征图即有x个卷积核 卷
  • 华为OD机试 - 字符串序列判定(Java)

    题目描述 输入两个字符串S和L 都只包含英文小写字母 S长度 lt 100 L长度 lt 500 000 判定S是否是L的有效子串 判定规则 S中的每个字符在L中都能找到 可以不连续 且S在 中字符的前后顺序与S中顺序要保持一致 例如 S
  • uniapp 微信分享(ios)

    ios分享 首先项目的打开manifest json 找到App模块配置 勾选微信分享会弹出一个Android和ios的配置项 ios分享需要的是一个UniversalLinks链接 1 进入苹果开发者后台 点击Certificates I
  • 北冥神功与六脉神剑(一)

    北冥神功与六脉神剑 言念及此 登时心下坦然 默默祷祝 神仙姊姊 你吩咐下来的事 段誉当然一定遵行不误 但愿你法力无边 逍遥派弟子早已个个无疾而终 战战兢兢的打开绸包 里面是个卷成一卷的帛卷 展将开来 第一行写着 北冥神功 字迹娟秀而有力 便
  • 如何解决:OSError: Unable to create file (unable to open file: name = ‘. et_classification.h5‘, errno = 2

    报错 OSError Unable to create file unable to open file name et classification h5 errno 22 error message Invalid argument f
  • 【深度学习工作站】CUDA + cuDNN + Tensorflow-gpu

    安装有两种路径 1 Anaconda简便安装 不需要安装CUDA和cuDNN 即使装了 Conda环境还是会重装CUDA和cuDNN 在清华镜像下载Anaconda3 新建环境后conda install tensorflow gpu 1
  • [ECharts] There is a chart instance already initialized on the dom.问题原因

    在使用vue绘图的时候 我设置间隔时间进行绘制 控制台一直警告 ECharts There is a chart instance already initialized on the dom 查看代码是因为获取了两次dom进行了初始化 m
  • Mac下 cobra安装

    Mac下 cobra安装 1 配置 bash profile export GOPATH PWD go export GOBIN GOPATH bin export PATH PATH GOBIN 2 在 GOPATH src go get
  • 刚体动力学

    文章目录 刚体状态 将某个物体从局部坐标系变化到全局坐标系 对时间求导 对矩阵求导 惯性 刚体属性 1 质心 计算方法 体素法 直接计算法 四面体体积 四面体的中心 2 惯性张量 世界坐标系中的惯性变量 刚体运动 力矩 刚体的固定属性 当前
  • c语言停车场

    include
  • Google Test(GTEST)使用入门(2)- 原生例子分析

    目录 一 原生例子路径 二 待测代码 三 主程序入口 四 测试用例代码 五 总结 一 原生例子路径 上篇我们已经介绍原生的例子在如下路径 googletest release 1 8 1 googletest samples 测试用例和待测
  • spring boot一个奇怪的错误(There was an unexpected error (type=Internal Server Error, status=500). Exceptio)

    今天运行spring boot的时候爆了这个错 There was an unexpected error type Internal Server Error status 500 Exception parsing document t
  • numpy--argsort含义及连续两个argsort用法

    官方文档 https docs scipy org doc numpy 1 15 0 reference generated numpy argsort html numpy argsort argsort函数返回的是数组值从小到大的索引值
  • 三极管的知识

    三极管的知识 在实际的电路中 三极管可以应用到很多的场景中 三极管最常用的功能是开关的作用 要利用其开关的作用 那么必须了解三极管的特性 B为基极 E为发射极 C为集电极 根据箭头的方向来判定三极管是NPN还是PNP 1 截止状态 当加在三
  • 【SpringSecurity】使用注解方式实现匿名访问

    SpringSecurity实现匿名访问的方式如下 spring security配置 link EnableGlobalMethodSecurity 如果想要启用spring方法级安全时 使用这个注解 author ruoyi Enabl
  • css常用选择器

    一 常用的css基本选择器 4种 1 标签选择器 结构 标签名 css属性名 属性值 作用 通过标签名 找到页面中所有的这类标签 设置样式 注意 1 标签选择器选择的是一类标签 而不是单独的一个 2 标签选择器无论嵌套关系有多深 都能够找到
  • AC自动机 (多模式匹配)

    AC自动机 感谢博主 https blog csdn net bestsort article details 82947639 感谢博主 https fanfansann blog csdn net article details 106