当前位置: 首页 > news >正文

AC自动机

AC自动机

该模型应用场景是什么样的?假如有一篇很长的文章,然后有一个敏感词表单,请从这篇文章里找出包含了哪些敏感词。即便是用KMP进行快速匹配,那也只能每次遍历整篇文章才能找到一种敏感词,KMP只适用于单一子串匹配,并且原串不能太大。AC自动机可以做到只需要遍历一遍就可以找到文章里包含的所有表单中的词。AC自动机利用了前缀树结构设计了一种不回退技巧,使得多匹配变得十分高效。

在下面的流程中,大家肯定会疑惑,为什么要这么设置呢? 对于这样的疑惑一点也不奇怪,因为我刚开始时也是一脸雾水。但是这不影响理解,大家姑且先别管为什么这么做,先把流程熟悉了之后再去理解为什么要这么做。

前缀树结构

和经典前缀树几乎一模一样,只是多了一些属性来帮助完成不回退。前缀树是给敏感词用的,每一个敏感词都需要将其插入到前缀树中。

    public static class Node{// 如果一个node的end为null,说明该结点不是任意一个字符串的结尾,或者是已经收集过答案的敏感词,// 我们不需要再重复收集了,所以将它设置为null// 如果end不为空,表示这个点是某个敏感词的结尾,end的值就是这个字符串,并且该敏感词还没收集过// 所以,非字符串结尾的结点的end也都是为nullpublic String end;// 只有在上面的end变量不为空的时候,logged才有意义。logged表示这个字符串之前有没有加入过答案// 因为一篇大文章里,同一个敏感词可能出现的次数不止1次,所以避免重复收集public boolean logged;// 这个fail域是最重要的属性,用它来帮助完成不回退。具体怎么设置下面会讲public Node fail;public Node[] nexts; // 这里默认所有都是由小写英文字母组成的字符串,a-->0  b-->1  z-->25public Node() {logged = false;end = null;fail = null;nexts = new Node[26];}}

我们采取的策略是先统一把所有敏感词插入到前缀树中,然后再利用宽度优先遍历一层一层地设置每个结点的 fail . 接下来用实例来讲解该过程。

设置fail指针域

假设给的敏感词列表是 ["bcde", "abcde", "de", "cde"] 那么建立好的前缀树如下图所示(实线是next域,虚线是fail域)

规定:根结点的fail指向null,根结点的直接后继结点的fail指向根。也就是说第1层和第2层的fail都设置好了,接下来就是一层一层设置了。

  1. 当我们来到第一条链的C结点时,我们需要看他的父结点的fail指针指向的结点是否有自己的这条路。C的父结点是通过C这条路来到C的,所以C看父结点的fail指向root,便问root是否也有C的路,一看发现有,于是C的fail就指向了第4条链的第一个结点C。如果父结点的fail指向的结点没有去C的路,那就继续从父结点的fail指向的结点再顺着fail跳一步,如果都来到了根结点还没找到的话,那么C的fail就指向根。

这就是设置fail指针的所有规则了,下面的图是设置好所有的fail指针后对位置稍微调整后的状态,因为不调整的话线条有点乱。蓝色内容就是每个结尾处字符记录的完整字符串,其余中间结点是没有的。F就表示当前敏感词是否被收集过,因为一开始都没收集过,所以都是false

下面是给整棵前缀树设置fail域的代码

public void build(){Queue<Node> queue = new LinkedList<>();queue.add(root);Node cur = null;Node cFail = null;while (!queue.isEmpty()){// 弹出某个结点,我们要处理的是它所有的孩子  我们没办法做到自己给自己设置fail,// 因为需要依靠父结点,而在层次遍历中,来到cur时,cur的父结点早就出队列了。所以我们只能// 通过cur为其孩子设置failcur = queue.poll(); for (int i = 0; i < 26; i++) { // 排查所有孩子if (cur.nexts[i] != null){ // 如果有i号儿子// 我们采用的逻辑是:先全部指向root,再单独处理那些有其他指向的cur.nexts[i].fail = root;cFail = cur.fail;// 根结点的孩子是不会执行该循环的,因为在第二层时,cur就是根,根的fail==nullwhile (cFail != null){ // 顺着fail循环一周去寻找 ==null 说明已经来到根结点的failif (cFail.nexts[i] != null){cur.nexts[i].fail = cFail.nexts[i];break;}cFail = cFail.fail;}queue.add(cur.nexts[i]);}}}
}

至此,整棵前缀树才彻底完成,接下来就是用给定的大文章在这棵前缀树上玩匹配了,接下来的流程才体现了AC自动机的绝妙之处!!!!

多匹配

假设给的大文章是 abcde 一开始cur指向前缀树根结点

  1. 每次遍历一个字符,我们要做的第一步就是安置好cur的去向,让cur去到了该去的位置后,第二步才是收集答案。所以现在第一步我们先安排cur的去向。我们发现cur有去往 a 的路,于是cur就往下跳到了 a ,完成了cur的设置,因为这里直接有路,所以就设置比较方便,当没路的时候cur的设置就有点麻烦了。
  2. cur跳好了,现在开始收集答案,这是整个模型最重要的机制!!! 用一个follow复刻此时cur的指向,然后让follow顺着cur的fail域所连接的链绕一圈,直到到了root才停止,在沿途一圈收集答案,cur的位置依然保持在原位。 但此时follow很轻易就跳到了root,根本没有答案。
  3. 遍历到大文章的1位置的字符b,cur此时在前缀树第一条链的a结点上,此时有去往b的路,然后跳到b,收集答案,也没收集到…
  4. 直接让cur加速来到d吧,因为上面都收集不到答案,所以略过。此时大文章的字符是e,而cur有去往e的路,于是来到了e,此时让follow==cur去收集答案,发现此时姐弟的logged域为False,并且其end域有内容,于是将abcde这个答案收集,并且将该结点的logged域设为True,然后follow顺着fail来到了第二条链的e结点,并且同意符合可以收集答案的条件,于是将bcde这个答案收集,并且将该结点的logged域设为True;再次顺着fail来到了第三条链的e结点,于是将cde这个答案收集,并且将该结点的logged域设为True;再次顺着fail来到了第四条链的e结点,于是将de这个答案收集,并且将该结点的logged域设为True;再次顺着fail来到root,至此循环结束。

有没有发现,我们只是遍历到了大文章的e位置,就将里面可能包含的所有敏感词全部找出来了,而且我们甚至都没有回退,根本不需要从头再遍历!!!而且如果大文章后面还有字符,那么cur就会沿着自己的位置继续遍历,这里需要注意,收集答案的时候cur是原地不动的。

再举一个例子,来充分体现AC自动机的机制。假设大文章是 abcdtbce

abcd就不讨论了,cur已经来到了第一条链的d结点,遍历下一个字符是t,而cur没有去t的路,所以我们沿着fail指针来寻找cur的去向,注意,此时还是处于给cur寻找位置的阶段,还没到收集答案的时候。一定要记住,只有让cur跳完了,才能收集答案!!! cur此时来到了第二条链的d结点,发现它也没有去往t的结点,于是又顺着这个d的fail来到了第三条链的d结点,也没有去t的路,于是来到了第四条链的d结点,也没有,于是顺着fail来到了根结点root,至此宣告了:cur一开始从根结点到此时的根结点所对应的大文章的这批字符对所有的敏感词匹配的彻底失败,也就是说abcdt匹配不出任何敏感词,才致使cur又重回到了根,是不是很奇妙,因为cur又可以重新匹配接下来的字符串了,并且是以开头的位置来匹配。

现在大家是不是对fail指针有一点感觉了。其实fail指针顾名思义就是,当匹配失败时应该走的路。回想一下,当我们用大文章匹配敏感词,匹配成功了若干个字符后,突然某个字符匹配不上了,这时候我们应该怎么做?是让遍历前缀树的指针重新回到根结点继续匹配吗?如果这样的话,那就是说我们彻底放弃了前面那些匹配成功的字符里是否包含其他敏感词的可能性,而是从失败的字符开始重新匹配,虽然失败了,但是只能说明匹配某个敏感词失败了,而不能说明匹配所有的敏感词失败了。

就像abcdt.....去匹配 abcde 这个敏感词时,来到t失败了,这只能说明匹配这个敏感词失败了,但是万一前面成功的字符还包含其他敏感词呢?如果此时让指针重新回到了前缀树根,那就是说我们放弃了以b、c、d开头去匹配敏感词的可能性。所以fail指针就是告诉你,我们不可以这么冲动地舍弃这么多可能性,而是尽可能地小心地舍弃可能性。 就像在树中那样,如果t失败了,指针会来到第二条链的d结点,因为bcd和abcd的共同后缀最长,这样我们就只舍弃了以a开头的可能性,并且在寻找以b开头的可能性,

下面是AC自动机的多匹配代码

public List<String> involvedWords(String content){char[] chars = content.toCharArray();Node cur = root;Node follow = null;int path = 0;List<String> res = new ArrayList<>();for (char c : chars) {// 虚线包围的代码其实就是给cur寻找要跳的位置,等cur跳好了之后,就让follow绕一圈收集答案。// -------------------------------------------------------------------------------path = c - 'a';// 如果当前字符在这条路上没配出来,就随着fail方向走向下条路径// 为啥要加一个cur != root 因为cur==root时再往fail走就到null了,我们的意思是最后最差也是回到根重新配while (cur.nexts[path] == null && cur != root)cur = cur.fail;// 跳出while时有两种可能cur = cur.nexts[path] == null ? root : cur.nexts[path];// 到这里时,cur就有两种情形:// 1) 现在来到的路径,是可以继续匹配的// 2) 现在来到的节点,就是前缀树的根节点// -------------------------------------------------------------------------------follow = cur;while (follow != root) { // 如果是第2种情形,那么该while不会执行,不影响后续if (follow.logged)  // 如果已经加过,直接跳出,因为后续的循环肯定也走过break;if (follow.end != null) {res.add(follow.end);follow.logged = true;}follow = follow.fail;}}return res;
}

相关文章:

AC自动机

AC自动机 该模型应用场景是什么样的&#xff1f;假如有一篇很长的文章&#xff0c;然后有一个敏感词表单&#xff0c;请从这篇文章里找出包含了哪些敏感词。即便是用KMP进行快速匹配&#xff0c;那也只能每次遍历整篇文章才能找到一种敏感词&#xff0c;KMP只适用于单一子串匹配…...

git入门

目录 1. git简介 1.1 git是什么 1.2 git与svn的区别 2. github 2.1 创建仓库 2.2 删除仓库 2.3 新建文件及文件夹 3. git的基本操作 3.1 配置账户及邮箱 3.2 git文件状态与工作区域 3.3 常用命令 3.4 克隆&#xff08;clone&#xff09; 3.5 查看git仓库的状态 3.…...

RK3568编译Android11和目录讲解

文章目录 前言一、下载android11源码二、环境搭建1.增加交换内存三、编译瑞芯微原厂源码四、目录讲解总结前言 本文记录在Ubuntu18.04中编译Android11,只有编译了源码,后面才能进行驱动的开发,有兴趣的小伙伴可以和我一起学习吧! 提示:以下是本篇文章正文内容,下面案例可…...

java泛型学习篇(二)

java泛型学习篇(二) 1 自定义泛型类 1.1 基本语法 Class 类型 <T,R,M...>{ //成员,其中...代表<>括号里面的参数可以有多个ja }1.2 注意点 1.2.1 属性和方法都是可以使用泛型的 T t;//属性使用泛型,合法public T getT() {return t;} //方法使用泛型,合法 publi…...

Java基础

Java基础Java基础一、课前问答二、概述三、Java的历史四、Java的特点五、计算机执行机制以及Java执行机制5.1 计算机的执行机制5.2 Java的执行机制六、常用DOS命令七、第一个Java程序八、包的使用九、编码规范十、注释Java基础 一、课前问答 1、什么是程序 2、什么是语言 3、什…...

骨骼控制(一)——动画动态节点(AnimDynamics)

文章目录一、引言二、骨骼控制三、UE蓝图中提供的骨骼控制节点——AnimDynamics动画蓝图节点1、什么是AnimDynamics动画蓝图节点①使用盒体计算惯性②使用约束来限制移动2、AnimDynamics节点的几种常用例子①单骨骼模拟②骨骼链模拟 <h2 id1>③群魔乱舞&#xff08;这是错…...

Linux系统下搭建maven环境

文章目录前述从官网下载安装包安装 maven修改maven配置修改环境变量测试前述 安装 maven 环境前&#xff0c;需要先安装 java 环境&#xff0c;如果没有安装 java 环境&#xff0c;可以参考&#xff1a;https://blog.csdn.net/weixin_45583303/article/details/118631855 从官…...

English Learning - L2 语音作业打卡 Day3 2023.2.23 周四

English Learning - L2 语音作业打卡 Day3 2023.2.23 周四&#x1f48c; 发音小贴士&#xff1a;&#x1f48c; 当日目标音发音规则/技巧&#xff1a;&#x1f36d; Part 1【热身练习】&#x1f36d; Part2【练习内容】&#x1f36d;【练习感受】&#x1f353;元音[ ɔ: ]&…...

RK3568平台开发系列讲解(驱动基础篇)GIC v3中断控制器

🚀返回专栏总目录 文章目录 一、什么是GIC二、GIC v3中断类型三、GIC v3基本结构3.1、Distributor3.2、CPU interface简介3.3、Redistributor简介3.4、ITS(Interrupt translation service)4、中断状态和处理流程沉淀、分享、成长,让自己和他人都能有所收获!😄 📢ARM多核…...

决策树、随机森林、极端随机树(ERT)

声明&#xff1a;本文仅为个人学习记录所用&#xff0c;参考较多&#xff0c;如有侵权&#xff0c;联系删除 决策树 通俗来说&#xff0c;决策树分类的思想类似于找对象。现想象一个女孩的母亲要给这个女孩介绍男朋友&#xff0c;于是有了下面的对话&#xff1a; 女儿&#x…...

软件测试之因果图法

因果图法 1. 概述 因果图法是一种**利用图解法分析输入条件、输出结果的各种组合情况,**从而设计测试用例的方法. 因果图法适用于有多个输入和多个输出&#xff0c;而且输入和输入之间有相互的组合关系&#xff0c;输入和输出之间有相互的制约和依赖关系. 使用场景和判定表…...

vue中子组件间接修改父组件传递过来的值

一、前言 Vue官方文档Props单向数据流讲解 Vue中遵循单向数据流&#xff0c;所有的 props 都遵循着单向绑定原则&#xff0c;props 因父组件的更新而变化&#xff0c;自然地将新的状态向下流往子组件&#xff0c;而不会逆向传递。这避免了子组件意外修改父组件的状态的情况&a…...

Java I/O

前言 关于IO, 想必你听过很多中I/O方式, 有的是OS视角的, 有的是JDK本身支持的, 有的是纯实现视角。但是作为一个developer, 我希望你能先搞清楚上下文之后, 再去理解内容, 否则容易抬杠。这个上下文有横向和纵向两个维度。纵向维度包括JDK底层, JDK上层包装库, 开发框架(如Ne…...

pytorch学习日记之图片的简单卷积、池化

导入图片并转化为张量 import torch import torch.nn as nn import matplotlib.pyplot as plt import numpy as np from PIL import Image mymi Image.open("pic/123.png") # 读取图像转化为灰度图片转化为numpy数组 myimgray np.array(mymi.convert("L"…...

【java基础】抽象类和抽象方法

文章目录基本介绍抽象类抽象方法使用总结基本介绍 在面向对象的概念中&#xff0c;所有的对象都是通过类来描绘的&#xff0c;但是反过来&#xff0c;并不是所有的类都是用来描绘对象的&#xff0c;如果一个类中没有包含足够的信息来描绘一个具体的对象&#xff0c;这样的类就…...

RDD的内核调度【博学谷学习记录】

RDD的依赖关系RDD的依赖: 指的一个RDD的形成可能是有一个或者多个RDD得出, 此时这个RDD和之前的RDD之间产生依赖关系在Spark中, RDD之间的依赖关系,主要有二种依赖关系:1- 窄依赖:目的: 为了实现并行计算操作, 并且提高容错的能力指的: 一个RDD上的一个分区的数据, 只能完整的交…...

二叉树——二叉搜索树的最小绝对差

二叉搜索树的最小绝对差 链接 给你一个二叉搜索树的根节点 root &#xff0c;返回 树中任意两不同节点值之间的最小差值 。 差值是一个正数&#xff0c;其数值等于两值之差的绝对值。 示例 1&#xff1a; 输入&#xff1a;root [4,2,6,1,3] 输出&#xff1a;1 示例 2&…...

git的使用(终端输入指令)下

文章目录前言1、git 分支创建分支查看分支切换分支合并分支删除分支2.提交到远程仓库远程提交链接一下自己仓库总结前言 上章链接 &#xff1a;git的使用&#xff08;终端输入指令&#xff09;上 我们接着上着来说 上章把 git 的 功能实现了一部分&#xff0c;本章我们接着上文…...

python使用influxdb-client管理InfluxDB的bucket

bucket的概念类似数据库的“库”&#xff0c;同时每个库中的数据都因为存在“时间戳”&#xff0c;每个数据都会有一个对应的时间点 influxdb-client-python官方github页面&#xff1a;https://github.com/influxdata/influxdb-client-python 管理bucket的官方示例&#xff1…...

【c++】模板2—类模板

文章目录类模板语法类模板与函数模板区别类模板中成员函数常见时机类模板对象做函数参数类模板与继承类模板成员函数类外实现类模板分文件编写类模板与友元类模板语法 类模板作用&#xff1a; 建立一个通用类&#xff0c;类中的成员数据类型可以不具体制定&#xff0c;用一个虚…...

React 第五十五节 Router 中 useAsyncError的使用详解

前言 useAsyncError 是 React Router v6.4 引入的一个钩子&#xff0c;用于处理异步操作&#xff08;如数据加载&#xff09;中的错误。下面我将详细解释其用途并提供代码示例。 一、useAsyncError 用途 处理异步错误&#xff1a;捕获在 loader 或 action 中发生的异步错误替…...

Leetcode 3576. Transform Array to All Equal Elements

Leetcode 3576. Transform Array to All Equal Elements 1. 解题思路2. 代码实现 题目链接&#xff1a;3576. Transform Array to All Equal Elements 1. 解题思路 这一题思路上就是分别考察一下是否能将其转化为全1或者全-1数组即可。 至于每一种情况是否可以达到&#xf…...

微信小程序 - 手机震动

一、界面 <button type"primary" bindtap"shortVibrate">短震动</button> <button type"primary" bindtap"longVibrate">长震动</button> 二、js逻辑代码 注&#xff1a;文档 https://developers.weixin.qq…...

GitHub 趋势日报 (2025年06月08日)

&#x1f4ca; 由 TrendForge 系统生成 | &#x1f310; https://trendforge.devlive.org/ &#x1f310; 本日报中的项目描述已自动翻译为中文 &#x1f4c8; 今日获星趋势图 今日获星趋势图 884 cognee 566 dify 414 HumanSystemOptimization 414 omni-tools 321 note-gen …...

CMake 从 GitHub 下载第三方库并使用

有时我们希望直接使用 GitHub 上的开源库,而不想手动下载、编译和安装。 可以利用 CMake 提供的 FetchContent 模块来实现自动下载、构建和链接第三方库。 FetchContent 命令官方文档✅ 示例代码 我们将以 fmt 这个流行的格式化库为例,演示如何: 使用 FetchContent 从 GitH…...

Java入门学习详细版(一)

大家好&#xff0c;Java 学习是一个系统学习的过程&#xff0c;核心原则就是“理论 实践 坚持”&#xff0c;并且需循序渐进&#xff0c;不可过于着急&#xff0c;本篇文章推出的这份详细入门学习资料将带大家从零基础开始&#xff0c;逐步掌握 Java 的核心概念和编程技能。 …...

实现弹窗随键盘上移居中

实现弹窗随键盘上移的核心思路 在Android中&#xff0c;可以通过监听键盘的显示和隐藏事件&#xff0c;动态调整弹窗的位置。关键点在于获取键盘高度&#xff0c;并计算剩余屏幕空间以重新定位弹窗。 // 在Activity或Fragment中设置键盘监听 val rootView findViewById<V…...

打手机检测算法AI智能分析网关V4守护公共/工业/医疗等多场景安全应用

一、方案背景​ 在现代生产与生活场景中&#xff0c;如工厂高危作业区、医院手术室、公共场景等&#xff0c;人员违规打手机的行为潜藏着巨大风险。传统依靠人工巡查的监管方式&#xff0c;存在效率低、覆盖面不足、判断主观性强等问题&#xff0c;难以满足对人员打手机行为精…...

从“安全密码”到测试体系:Gitee Test 赋能关键领域软件质量保障

关键领域软件测试的"安全密码"&#xff1a;Gitee Test如何破解行业痛点 在数字化浪潮席卷全球的今天&#xff0c;软件系统已成为国家关键领域的"神经中枢"。从国防军工到能源电力&#xff0c;从金融交易到交通管控&#xff0c;这些关乎国计民生的关键领域…...

Leetcode33( 搜索旋转排序数组)

题目表述 整数数组 nums 按升序排列&#xff0c;数组中的值 互不相同 。 在传递给函数之前&#xff0c;nums 在预先未知的某个下标 k&#xff08;0 < k < nums.length&#xff09;上进行了 旋转&#xff0c;使数组变为 [nums[k], nums[k1], …, nums[n-1], nums[0], nu…...