当前位置: 首页 > 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;用一个虚…...

别再死记硬背SMO公式了!用Python手写一个SVM分类器,带你一步步拆解SMO核心逻辑

用Python手写SVM分类器&#xff1a;代码驱动理解SMO算法核心在机器学习领域&#xff0c;支持向量机(SVM)以其优秀的分类性能和坚实的数学基础著称。然而&#xff0c;许多学习者在理解其核心算法——序列最小优化(SMO)时&#xff0c;往往被复杂的数学推导所困扰。本文将采用一种…...

HFSS仿真结果怎么看?一文读懂S参数与电场图,让你的T型波导分析不再迷茫

HFSS仿真结果深度解析&#xff1a;从S参数到电场图的工程实践指南面对HFSS仿真生成的复杂数据图表&#xff0c;许多工程师常陷入"看得见数据却读不懂含义"的困境。本文将带您穿透数据表象&#xff0c;掌握T型波导性能分析的核心方法论。1. S参数&#xff1a;波导性能…...

AI开始替人办事后,最危险的不是模型不够强,而是它把旧资料当真了

AI开始替人办事后&#xff0c;最危险的不是模型不够强&#xff0c;而是它把旧资料当真了2026年真正值得重视的AI底层能力&#xff0c;是让模型知道该信谁 你有没有发现一个很扎心的变化。 以前我们用AI&#xff0c;最怕它不会。 现在我们用AI&#xff0c;最怕它太会了。 它能写…...

MongoDB Limit 与 Skip 方法详解

MongoDB Limit 与 Skip 方法详解 引言 MongoDB 是一个高性能、可伸缩的文档存储系统,它提供了强大的数据存储和查询功能。在处理大量数据时,Limit 与 Skip 方法是 MongoDB 中常用的查询优化工具。本文将详细介绍 MongoDB 中的 Limit 与 Skip 方法,包括其基本用法、性能影响…...

CPU架构启发的智能仓储布局优化实践

1. 仓库布局优化的核心挑战与创新机遇在物流仓储领域&#xff0c;拣货环节通常占据运营成本的55%-65%&#xff0c;而其中约50%的时间消耗在无效行走路径上。传统矩形仓库布局虽然易于规划和施工&#xff0c;但其正交的通道设计导致拣货员需要频繁进行90度转向&#xff0c;这种&…...

基于随机森林的低成本传感器机器学习校准实践指南

1. 项目概述&#xff1a;当低成本传感器遇上机器学习校准在物联网和智能感知系统铺天盖地的今天&#xff0c;低成本传感器几乎无处不在。从监测办公室的空气质量&#xff0c;到追踪城市街道的噪音污染&#xff0c;再到农业大棚里的温湿度控制&#xff0c;这些价格亲民的“小眼睛…...

武汉国电华美16875kVA串联谐振试验装置,这手活儿细

在超高压变电站和长距离电缆的现场&#xff0c;交流耐压试验是检验设备绝缘的“最后一关”。这位老师傅经手过不少大工程&#xff0c;他说&#xff0c;面对GIS、大型变压器这些“大块头”电容性试品&#xff0c;能不能顺利“过关”&#xff0c;往往就看串联谐振装置顶不顶得住。…...

特定任务需求场景下的过约束并联机构构型设计与控制方法【附代码】

✨ 长期致力于曲面加工、构型综合、运动学和动力学建模、性能评价、多目标优化、滑模控制、鲁棒控制、视觉传感技术研究工作&#xff0c;擅长数据搜集与处理、建模仿真、程序编写、仿真设计。 ✅ 专业定制毕设、代码 ✅ 如需沟通交流&#xff0c;点击《获取方式》 &#xff08;…...

USB数据隔离器DIY:物理切断数据线,防范充电攻击

1. 移动设备充电安全&#xff1a;一个被忽视的“物理后门”你可能每天都在做这件事&#xff1a;手机或平板电脑电量告急&#xff0c;随手拿起一根数据线&#xff0c;插在办公室的公共电脑、机场的充电站&#xff0c;甚至是朋友提供的充电宝上。这看起来再平常不过了&#xff0c…...

哪款台灯护眼效果最好孩子用?实测口碑爆款护眼灯品牌,买前必看

哪款台灯护眼效果最好孩子用&#xff1f;作为家长&#xff0c;最揪心的就是孩子的视力问题。有数据显示&#xff0c;现在孩子近视率越来越高&#xff0c;小学就有不少戴眼镜的&#xff0c;中学更是过半&#xff0c;看着实在让人担心。 孩子每天低头写作业、看书&#xff0c;灯光…...