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

基于SpringCloud的可靠消息最终一致性03:项目骨架代码(下)

上一节把整个项目的演示内容、项目结构、POM文件和配置文件都讲完了,接下来继续。 先安装并启动Nacos,然后在其中建立一个名为xiangwang-payment-dev.yaml的配置文件,内容为: # 指定运行环境 spring:autoconfigure:exclude: com.alibaba.druid.spring.boot.autoconfigure.D…...

linux如何彻底的删除文件

一、使用rm命令删除 直接用rm 先用ls -alt看下文件信息及拥有者等 可以看到拥有者是eve用户&#xff0c;所以在eve用户的终端中rm命令即可&#xff0c; 如果是root或者其他&#xff0c;则优先用root或其他账号进行删除 (base) eveEve:~$ ls -alt a.txt -rw-rw-r-- 1 eve eve …...

数据仓库Hive的安装和部署

1&#xff09;去apache.hive.org官网下载hive 目前hive主要有三大版本&#xff0c;Hive1.x、Hive2.x、Hive3.x Hive1.x已经2年没有更新了&#xff0c;所以这个版本后续基本不会再维护了&#xff0c;不过这个版本已经迭代了很多年了&#xff0c;也是比较稳定的 Hive2.x最近一直…...

Python调用CANoe常见问题

一、Win32com已经安装成功但是在pycharm中提示错误 No module named win32com.clientPyCharm中出现unresolved reference的解决方法 一直提示需要升级pip版本Pywin32已成功安装,但仍提示没有win32com模块...

一起Talk Android吧(第五百零七回:图片滤镜ImageFilterView)

文章目录背景介绍功能介绍图片滤镜图片圆角图片缩放图片旋转图片平移各位看官们大家好&#xff0c;上一回中咱们说的例子是"如何调整组件在约束布局中的角度",这一回中咱们说的例子是" 图片滤镜ImageFilterView"。闲话休提&#xff0c;言归正转&#xff0c…...

Java 解释器和即时解释器(JIT)之间的区别

区别是&#xff1a; 翻译 .class &#xff08;字节码文件&#xff09; 的粒度和方式不同 解释器是一个逐条解释并执行字节码指令的组件&#xff0c;每次**只翻译一条**指令并执行&#xff0c;然后再翻译下一条指令。 它的翻译粒度是一条指令&#xff0c;而且是按需翻译&#x…...

Acwing 蓝桥杯 第二章 二分与前缀和

今天来补一下之前没写的总结&#xff0c;题是写完了&#xff0c;但是总结没写感觉没什么好总结的啊&#xff0c;就当打卡了789. 数的范围 - AcWing题库思路&#xff1a;一眼二分&#xff0c;典中典先排个序&#xff0c;再用lower_bound和upper_bound维护相同的数的左界和右界就…...

CSDN原力增长规则解读 实测一个月

CSDN原力越来越难了&#xff0c;当然&#xff0c;这对生态发展来说也是好事。介绍下原力增长有哪些渠道吧。发布原创文章&#xff1a;10分/次&#xff0c;每日上限为15分、2篇回答问题&#xff1a;1分/次&#xff0c;每日上限2分&#xff0c;2回答发动态&#xff1a;1分/次&…...

HDMI协议介绍(三)--InfoFrame

目录 Auxiliary Video information (AVI) InfoFrame AVI InfoFrame包结构 Header Body 举个例子 附录 Audio InfoFrame Audio InfoFrame包结构 Header Body Vendor Specific InfoFrame Vendor Specific InfoFrame包结构 Header Body AVI/AUDIO/VSI Infoframe都…...

【RocketMQ】源码详解:Broker端消息储存流程、消息格式

消息存储流程 入口&#xff1a; org.apache.rocketmq.remoting.netty.NettyRemotingAbstract#processRequestCommand org.apache.rocketmq.broker.processor.SendMessageProcessor#asyncProcessRequest 消息到达broker后会经过netty的解码、消息处理器等&#xff0c;最后根据…...

成都网络推广运营公司/进一步优化营商环境

打开终端 重启apache&#xff1a;sudo /usr/sbin/apachectl restart 关闭apache&#xff1a;sudo /usr/sbin/apachectl stop 开启apache&#xff1a;sudo /usr/sbin/apachectl start转载于:https://www.cnblogs.com/greywolf/p/4580329.html...

做网站入什么科目/自媒体平台app下载

6月1日&#xff0c;《网络安全法》正式开始实施。作为我国第一部全面规范网络空间安全管理问题的基础性法律&#xff0c;这部法律的实施引发外媒关注&#xff0c;部分欧美相关企业、有政府背景的行业协会、政府部门以及主流媒体&#xff0c;对《网络安全法》表示了不同程度的担…...

wordpress 教育类主题/推广策划方案怎么做

摘要&#xff1a;CSS中的边框用户登录批改老师&#xff1a;西门大官人批改时间&#xff1a;2019-02-24 13:56:24老师总结&#xff1a;作业完成的不错&#xff0c;最好上传一下运行的效果图...

免费的网站程序哪里好/公众号引流推广平台

Error ORA-03113: 通信通道的文件结尾进程 ID: 2232会话 ID: 1250 序列号: 这是oracle 报的错误&#xff0c; 可能这个03113这个编码的错误有很多。 但是要找到是什么原因就需要根据2232这个编码找到 这个日志文件 D:\app\Administrator\diag\rdbms\orcl\orcl\trace\orcl_ora_2…...

网站推广优化如何做/北京seo学校

拆书&#xff0c;读书&#xff0c;帮你选技术书。橡皮擦 “读” “选” 技术书。 打开任意一个购书网站都包含着大量的技术书籍&#xff0c;如何选到一本好技术书成了我们打工人的难题。 很早以前萌生过这样一个想法&#xff0c;如果有人帮我先读一下技术书籍&#xff0c;告诉…...

自己做黄网站犯法吗/今日军事新闻报道

处理 由于不是 按到 insert 键导致的&#xff0c;所以怎么按 insert 键都没用 是由于 装了 Vs Vim 插件导致的&#xff0c;把插件卸载或者禁用进行 再次打开 VS 就可以了 步骤如图所示&#xff1a;...