AC自动机

news2024/11/24 20:52:22

AC自动机

AC自动机是干嘛的?

我有一个敏感词数组,里面装的是所有的敏感词,还有一篇大文章,我要求出大文章里面所有的敏感词。

敏感词数组本身的组织是一颗前缀树。

AC自动机就是在前缀树的基础上做升级。

流程

  • 我们在前缀树的基础上给每一个节点加上fail指针并且做出规定:头节点的fail指针一定指向null

  • 头节点的下级直接节点的fail指针一律指向头部

  • 整颗前缀树全部建立完毕之后,再去建立fail指针

  • 以下是其他节点的定义规则,看图说话。

请添加图片描述

  • 假设有一个节点X,X的父亲节点P,P的fail指针指的是谁,看图可以知道指向的是头节点。

  • X的父亲节点到X的路径存放的是b,因此我询问X的父亲节点的fail指针指向的节点,也就是图中的头节点,你的路径中有没有b,可以看到头节点有的路径是a, c, e,没有b

  • 于是继续往跳,头部节点的fail指针指向的节点是null,null当然不会有b路径,因此X的fail指针直接指向头部

  • 再看S节点。

请添加图片描述

  • S的父亲节点X的fail指针指向的节点是头节点,它们之间的路径是c,而头节点有c这个路径,所以X的fail指针指向的是头节点的以c为路径的孩子节点,如图。

请添加图片描述

AC自动机的fail指针的作用

我们再来画一个图:

请添加图片描述

fail指针的含义比较抽象,但是我们还是尝试去概括一下:

当字符串无法匹配时,我们有最后一个字符,我们命名为last,当必须以last结尾时,与字符串拥有同一后缀的最长的字符串,fail指针的作用就是方便的找到这样一个字符串。

我们看到上图:

有节点X,假设字符串abc就是我们无法匹配成功的字符串。fail指针指向的节点和头节点连接而成的路径是c,那么这个字符串c实际上就是与abc拥有同一后缀并且最长的字符串。

在这里插入图片描述

有节点Y,假设字符串abcd就是我们无法匹配成功的字符串。Y的fail指针指向的节点与头节点连成的字符串是cd,那么cd就是与abcd拥有相同最长后缀的字符串,与abcd拥有相同后缀的字符串还有c,但是c没有cd长,所以fail指针没有指向另一头的节点。

大文章敏感词匹配

请添加图片描述

  • 我们有大文章abcdex,我们对着这个AC自动机从0位置开始进行匹配,发现只能匹配到字符串abcde,因此得出结论,从0位置开始匹配,是无法匹配出敏感词的。
  • 我们匹配失败了,只能匹配到字符串abcde,此时的节点是X,这时候,我们就跳往X的fail指针指向的位置的节点Y。
  • 然后我们从头节点到Y的字符串是cde,因此,我们得到了最长的前缀保留,看,是不是跟KMP算法非常类似,AC自动机不过是在前缀树上的KMP算法。

AC自动机的代码实现

// 前缀树的节点
public class Node {
    // 如果一个 node,end为空,不是结尾
    // 如果end不为空,表示这个点是某个字符串的结尾,end的值就是这个字符串
    public String end;
    // 只有在上面的end变量不为空的时候,endUse才有意义
    // 表示,这个字符串之前有没有加入过答案, 防止答案收集重复,但是在业务场景中这个是没有必要的
    public boolean endUse;
    public Node fail;
    public Node[] nexts;

    public Node() {
        endUse = false;
        end = null;
        fail = null;
        // 这里默认是小写
        nexts = new Node[26];
    }
}
public class ACAutomation {
    private Node root;

    public ACAutomation() {
        root = new Node();
    }

    public void insert(String s) {
        char[] str = s.toCharArray();
        Node cur = root;
        int index = 0;
        for (int i = 0; i < str.length; i++) {
            index = str[i] - 'a';
            if (cur.nexts[index] == null) {
                Node next = new Node();
                cur.nexts[index] = next;
            }
            cur = cur.nexts[index];
        }
        cur.end = s;
    }

    // 宽度优先遍历
    public void build() {
        Queue<Node> queue = new LinkedList<>();
        queue.add(root);
        Node cur = null;
        Node cfail = null;
        while (!queue.isEmpty()) {
            // 当前节点弹出
            // 当前节点的所有后代加入到队列里面去
            // 当前节点给它的子去设置fail指针
            // cur -> 父亲
            cur = queue.poll();
            for (int i = 0; i < 26; i++) { // 所有的路
                if (cur.nexts[i] != null) { // 找到所有有效的路
                    // 我先设置为root,找到了就设置为别人,没找到就继续保持
                    cur.nexts[i].fail = root;
                    cfail = cur.fail;
                    while (cfail != null) {
                        if (cfail.nexts[i] != null) {
                            cur.nexts[i].fail = cfail.nexts[i];
                            break;
                        }
                        cfail = cfail.fail;
                    }
                    queue.add(cur.nexts[i]);
                }
            }
        }
    }

    // 大文章:content
    public List<String> containWords(String content) {
        char[] str = content.toCharArray();
        Node cur = root;
        Node follow = null;
        int index = 0;
        List<String> ans = new ArrayList<>();
        for (int i = 0; i < str.length; i++) {
            index = str[i] - 'a'; // 路
            // 如果当前字符在这条路上没配出来,就随着fail方向走向下条路径
            while (cur.nexts[index] == null && cur != root) {
                cur = cur.fail;
            }
            // 1) 现在来到的路径,是可以继续匹配的
            // 2) 现在来到的路径,就是前缀树的根节点
            cur = cur.nexts[index] != null ? cur.nexts[index] : root;
            follow = cur;
            // follow就是用来方便"逛"一圈儿的
            while (follow != root) {
                // 当我遇到过这个环,下次再次遇到的时候就直接break了
                if (follow.endUse) {
                    break;
                }
                // 不同的需求在这一段之间进行修改
                if (follow.end != null) {
                    ans.add(follow.end);
                    follow.endUse = true;
                }
                // 不同的需求,在这一段之间修改
                follow = follow.fail;
            }
        }
        return ans;
    }
}

我们来讲解一下void build()代码的含义:

  • 当队列里面的节点弹出的时候,我们把这个节点的所有子节点的fail指针设置好,因为子节点的fail指针是取决于父节点的,而我们是从父节点开始遍历的,所以这个过程是父节点弹出的时候,把子节点的fail指针都设置好。

  • 这个build代码是如何维护最长的,也就是这个代码是如何维护fail指针的正确含义的?

    答:这个代码可以自动维护,不需要做其他的事情,因为这有点儿动态规划的意思,我的父亲维护的是最长的,那么我维护的就自动是最长的,而头节点是null,大概就是这样一个意思吧。

在进行匹配的时候,我们每到达一个节点,都要把这个节点的fail指针全部逛一圈,找到所有的敏感词。

        follow = cur;
        // follow就是用来方便"逛"一圈儿的
        while (follow != root) {
            // 当我遇到过这个环,下次再次遇到的时候就直接break了
            if (follow.endUse) {
                break;
            }
            // 不同的需求在这一段之间进行修改
            if (follow.end != null) {
                ans.add(follow.end);
                follow.endUse = true;
            }
            // 不同的需求,在这一段之间修改
            follow = follow.fail;
        }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/115386.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

已来到 “后云原生时代” 的我们,如何规模化运维?

文&#xff5c;李大元 &#xff08;花名&#xff1a;达远&#xff09; Kusion 项目负责人 来自蚂蚁集团 PaaS 核心团队&#xff0c;PaaS IaC 基础平台负责人。 本文 4331 字 阅读 11 分钟 PART. 1 后云原生时代 距离 Kubernetes 第一个 commit 已经过去八年多了&#xff0c…

chrome extensions mv3与mv2比较 执行eval

文章目录背景1、mv3版本与mv2版本之间的一些区别2、解决mv3版本DOM交互 & JS执行问题2.1、关于引入eval52.2、关于在background.js执行script脚本3、background执行fetch调用URL参考背景 老的扩展项目使用的是mv2版本的API&#xff0c;计划升级mv3版本的时候遇到了下面的问…

MySQL索引为什么使用B+树,而不用二叉树、红黑树、哈希表、B树?

索引是帮助MySQL高效获取数据的排好序的数据结构。 索引数据结构&#xff1a; 1.二叉树 2.红黑树 3.Hash表 4.B-Tree 1. 二叉查找树&#xff08;Binary Search Trees&#xff09; 左节点比父节点要小&#xff0c;右节点比父节点要大。它的高度决定的查找效率。 如果某一列数…

Java面试题-框架篇

框架篇 文章目录框架篇1. Spring refresh 流程2. Spring bean 生命周期3. Spring bean 循环依赖解决 set 循环依赖的原理4. Spring 事务失效5. Spring MVC 执行流程6. Spring 注解7. SpringBoot 自动配置原理8. Spring 中的设计模式1. Spring refresh 流程 要求 掌握 refresh…

【服务器数据恢复】EMC存储raid5多块磁盘掉线的数据恢复案例

服务器数据恢复环境&#xff1a; EMC某型号存储&#xff1b; 8块硬盘组成raid5磁盘阵列。 服务器故障&#xff1a; raid5磁盘阵列中2块硬盘离线&#xff0c;服务器崩溃&#xff0c;上层应用不可用。 服务器数据恢复过程&#xff1a; 1、数据恢复工程师将故障存储设备内的所有硬…

嵌入式开发--PID控制

PID简介 讲解PID的文章书籍很多&#xff0c;本文就不详细讲了&#xff0c;只讲一下我在学习过程中不容易理解的一些问题点&#xff0c;以供大家参考。比如很多书籍对于PID&#xff0c;只讲了计算&#xff0c;但是最后计算出来的值如何应用&#xff0c;则完全不讲&#xff0c;当…

C++:设计一个学生学籍管理系统,设计相关信息,并执行一些计算和文件操作

题目&#xff1a; 设计一个学生学籍管理系统&#xff1a; 学生信息包括&#xff1a;姓名、学号、性别和英语、数学、程序设计、体育成绩。数据录入支持键盘输入和文件导入&#xff1b;同时支持导入输入&#xff0c;如自动列出“姓名、学号、性别”&#xff0c;而成绩部分由键盘…

SFP、SFP+、SFP28、QSFP+和QSFP28之间的区别以及不同场景的使用选型

SFP、SFP+、SFP28、QSFP+和QSFP28之间的区别以及不同场景的使用选型。 SFP、SFP+、SFP28、QSFP+和QSFP28这些光模块类型对专业人员来说并不陌生,这些热拔插模块都可用于连接网络交换机和其他网络设备(如服务器或收发器)进行数据传输。但你了解这些模块的具体区别吗?QSFP28…

Python爬虫爬取某电影排行榜图片实例

今天继续给大家介绍Python 爬虫相关知识&#xff0c;本文主要内容是Python爬虫爬取某电影排行榜图片实例。 一、要求分析 在上文Python爬虫爬取某电影排行实例中&#xff0c;我们已经能够使用Python程序爬取某电影排行榜中的电影名称。今天&#xff0c;我们来尝试以下下载电影…

入职第一天,HR拿了一个橙子进门说:你的学历不是统招本科,不符合公司要求,给你个橘子,走吧!...

今天来讲一件又好笑又好气的事&#xff0c;这是一位网友的亲身经历&#xff1a;入职第一天&#xff0c;入职材料填到一半&#xff0c;HR拿了一个橙子进门&#xff0c;放在桌子上开口说&#xff1a;抱歉&#xff0c;由于之前工作失误&#xff0c;没确认你的第一学历不是统招本科…

RK3568平台开发系列讲解(调试篇)Linux相关日志分析

🚀返回专栏总目录 文章目录 一、dmesg二、动态打印案例沉淀、分享、成长,让自己和他人都能有所收获!😄 📢本文我们要介绍Linux内核的日志分析。 一、dmesg printk是在内核中运行的向控制台输出显示的函数,Linux内核首先在内核空间分配一个静态缓冲区,作为显示用的空…

​杭州蓝然创业板IPO终止:应收账款、存货账面高,楼永通为实控人​

近日&#xff0c;杭州蓝然技术股份有限公司&#xff08;下称“杭州蓝然”&#xff09;向深圳证券交易所提交了撤回在创业板上市申请文件的申请。同时&#xff0c;其保荐机构也撤回保荐。12月23日&#xff0c;深圳证券交易所做出决定&#xff0c;终止对杭州蓝然在创业板IPO的审核…

学习笔记:Java 并发编程②

若文章内容或图片失效&#xff0c;请留言反馈。 部分素材来自网络&#xff0c;若不小心影响到您的利益&#xff0c;请联系博主删除。 视频链接&#xff1a;https://www.bilibili.com/video/av81461839配套资料&#xff1a;https://pan.baidu.com/s/1lSDty6-hzCWTXFYuqThRPw&am…

Matlab 实现磁测数据日变改正

1 算法 算法来自于GEMLink 5.2的帮助文档&#xff0c;这个文档基本解决了算法问题。 GemLink日变改正模块界面 1.1 概述 日变改正模块旨在执行磁力日变数据计算&#xff0c;而不用在仪器上进行日变&#xff08;仪器是未经过校正的原始数据&#xff09;。这个模块要求已经保…

[ CTF ] WriteUp-2022年春秋杯网络安全联赛-冬季赛

没啥时间打比赛就大概看了一下做了几题 文章目录[Misc] reindeer game[Misc] 调查问卷[Web] ezphp[Misc] reindeer game ​ 这题最简单的方法就是玩游戏了&#xff0c;然后直接出flag flag{82a2acb6-9803-4936-92db-f1431d90c6d1} [Misc] 调查问卷 flag{the_harder_u_struggl…

故事的开始:RaidAI

目录引言RapidAI/RapidOCRRapidAI/YOLO2COCORapidOcrAndroidOnnxRapidAI/RapidOcrNcnnRapidAI/PaddleOCRModelConverterRapidAI/RapidTTSRapidAI/RapidASRRapidAI/RapidPix2Pix引言 RapidAI是一个将AI模型应用到工程中的开源组织&#xff0c;致力于搭建AI模型从学术界到工程界…

RabbitMQ:消息分发模型

Work queues&#xff0c;也被称为 Task queues&#xff0c;任务模型&#xff0c;当消息处理比较耗时时的时候&#xff0c;可能产生消息的速度会远远大于消息的消费速度&#xff0c;消息会堆积越来越多&#xff0c;无法及时处理&#xff0c;此时就可以使用work模型&#xff1a;让…

圣诞的荒诞小故事并记录互联网协议-五层模型

今天敲代码敲着敲着灵光乍现&#xff0c;突然一个荒诞的故事&#x1f4a1;映入脑海。 1.未来和过去&#xff1a; 人高度发达&#xff08;以下称之为渡&#xff09; 渡可以打开时空穿越过去&#xff08;以下称之为旧迹&#xff09;&#xff0c;并且可以进随心所欲的来去自如&a…

【微服务】Nacos的寻址机制

目录 一、 Nacos的寻址机制 1、前提 2、设计 3、内部实现 3.1、单机寻址 3.2、文件寻址 3.3、地址服务器寻址 4、未来可扩展点 4.1、集群节点自动扩缩容 &#x1f496; Spring家族及微服务系列文章 一、 Nacos的寻址机制 1、前提 Nacos 支持单机部署以及集群部署&am…

xxl-job基本原理以及底层流程讲解

任务执行策略 任务阻塞策略 整体架构部署 这里主要讲解下每个模块的作用 调度模块&#xff1a;负责管理调度信息&#xff0c;按照调度配置发出调度请求&#xff0c;自身不承担任何业务代码。调度系统于任务解耦&#xff0c;提高了系统可用性和稳定性&#xff0c;同时调度系统性…