使用AC自动机实现敏感词过滤(java)

主要分成2部分

  1. trie树的构建(前缀树,字典树)
  2. fail指针的构建

1. trie 树

  • 同一层级不会有重复的字符
  • 敏感词的最后一个字符会标记,并携带敏感词的长度

2. fail 指针的构建

fail 指针是指在某个分支匹配失败后,重新指向关联的其他分支上

  • 构建fail指针的遍历为层次遍历(广度优先)
  • root节点的fail指针指向null
  • 如果当前节点的父节点的fail指针指向的节点下存在与当前节点一样的子节点,则当前节点的fail指针指向该子节点,否则指向root节点
  • 如果当前节点的失败节点也是end节点,则将失败节点的长度信息合并到当前节点
package com.xx.xxx.匹配算法;

import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.util.CollectionUtils;

import java.util.*;

/**
 * 多模匹配算法,AC自动机
 * 给出一个字符串,匹配多个敏感词
 * demo:
 * 敏感词库    he say her shr she
 * 被检测字符  sherhsay
 * 检测结果    she her he say
 */
public class AC {
    @Data
    @NoArgsConstructor
    public static class ACNode {
        Character am;
        // 子节点
        Map<Character, ACNode> children = new HashMap<>();
        ACNode failNode;
        // 存储匹配到的敏感字符长度
        List<Integer> wordLength = new ArrayList<>();
        // 是否是结束字符
        private boolean endOfWord;

        public ACNode(Character am) {
            this.am = am;
        }

        public String toString() {
            return "ACNode{" +
                    "am=" + am + "," +
                    "children=" + children +
                    ",wordLength=" + wordLength +
                    '}';
        }


        // 构建字典树
        public static void insert(ACNode root, String s) {
            ACNode temp = root;
            char[] chars = s.toCharArray();
            for (int i = 0; i < chars.length; i++) {
                if (!temp.children.containsKey(chars[i])) {
                    temp.children.put(chars[i], new ACNode(chars[i]));
                }
                temp = temp.children.get(chars[i]);
                // 如果是最后一个字符,则设置为结束字符
                if (i == chars.length - 1) {
                    temp.setEndOfWord(true);
                    temp.getWordLength().add(chars.length);
                }
            }
        }

        // 构建失败指针
        public static void buildFailPoint(ACNode root) {
            // 第一层的失败指针都是执行root,直接让第一层进入队列,方便 BFS
            Queue<ACNode> queue = new LinkedList<>();
            Map<Character, ACNode> childrens = root.getChildren();
            for (ACNode acNode : childrens.values()) {
                queue.offer(acNode);
                acNode.setFailNode(root);
            }
            // 构建剩余节点的失败指针,按层次遍历
            while (!queue.isEmpty()) {
                ACNode pnode = queue.poll();
                childrens = pnode.getChildren();
                Set<Map.Entry<Character, ACNode>> entries = childrens.entrySet();
                for (Map.Entry<Character, ACNode> entry : entries) {
                    // 当前节点的字符
                    Character key = entry.getKey();
                    ACNode cnode = entry.getValue();
                    // 如果当前节点的父节点的fail指针指向的节点下存在与当前节点一样的子节点,则当前节点的fail指针指向该子节点,否则指向root节点
                    if (pnode.failNode.children.containsKey(key)) {
                        cnode.setFailNode(pnode.failNode.children.get(key));
                    } else {
                        cnode.setFailNode(root);
                    }
                    // 如果当前节点的失败节点的wordLength不为空,则将当前节点的失败节点wordLength 合并到到当前节点的wordLength中
                    if (!CollectionUtils.isEmpty(cnode.failNode.wordLength)) {
                        cnode.getWordLength().addAll(cnode.failNode.wordLength);
                    }
                    queue.offer(cnode);
                }
            }

        }

        public static void query(ACNode root, String s) {
            ACNode temp = root;
            char[] chars = s.toCharArray();
            for (int i = 0; i < s.length(); i++) {
                // 如果这个字符串在当前节点的孩子节点找不到,且当前节点的fail指针不是null,则去失败指针去查找
                while (!temp.getChildren().containsKey(chars[i]) && temp.failNode != null) {
                    temp = temp.failNode;
                }
                // 如果当前节点有这个字符,则将temp替换为下面的孩子节点
                if (temp.getChildren().containsKey(chars[i])) {
                    temp = temp.getChildren().get(chars[i]);
                } else {
                    // 如果temp的failNode==null,则为root节点
                    continue;
                }
                // 如果检测到节点是结束字符,则将匹配到的敏感字符打印
                if (temp.isEndOfWord()) {
                    handle(temp, s, i);
                }
            }
        }

        public static void handle(ACNode node, String word, int curPoint) {
            for (Integer wordLen : node.wordLength) {
                int start = curPoint - wordLen + 1;
                String mathStr = word.substring(start, curPoint + 1);
                System.out.println("位置信息:[" + start + "," + curPoint + "),敏感词=" + mathStr);
            }
        }


        public static void main(String[] args) {
            ACNode root = new ACNode('-');
            root.failNode = null;
            insert(root, "黑社会");
            insert(root, "色情");
            insert(root, "黑暗任务");
            insert(root, "黑色会");
            insert(root, "国民党");
            insert(root, "国民");
            buildFailPoint(root);
            query(root, "按计划多久啊是德国 按时间大概是国民党卡的韩国阿克苏接电话ask接电话ask的话asks对话框,节点哈桑打算离开的机会撒的撒" +
                    "旦和了色情垃圾上单拉萨的黑色会啊是的噶时间大概时间大概是孤岛惊魂过去问工业国国民党");
        }


    }
}

参考B站大神 LDLD是程序员 的视频

【全程干货】程序员必备算法!AC自动机算法敏感词匹配算法!动画演示讲解,看完轻松掌握,面试官都被你唬住!!_哔哩哔哩_bilibili

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

碰撞的小球(Colliding balls)

效果如下&#xff1a; 代码: #include <bits/stdc.h> #include <graphics.h>//必须库 #include <time.h> using namespace std; int main() {initgraph(650,400);//背景图大小circle(100,100,40);fillcircle(200,200,10);//球的数据srand(time(NULL));int …

Leetcoder Day37| 动态规划part04 背包问题

01背包理论基础 面试掌握01背包&#xff0c;完全背包和重背包就够用了。 背包问题的理论基础重中之重是01背包&#xff0c;一定要理解透&#xff01; 01 背包 有n件物品和一个最多能背重量为w 的背包。第i件物品的重量是weight[i]&#xff0c;得到的价值是value[i] 。每件物品…

[Redis]——Redis命令手册set、list、sortedset

&#x1f333;List类型常见命令 LPUSH / RPUSH [KEY] [element] …… 向列表左侧或者右侧插入一个或多个元素 LPOP / RPOP [key] 删除左边或者右边第一个元素 LRANGE [key] start end 返回索引start到end的元素&#xff08;索引从0开始&#xff09; BLPOP / BRPOP [key] [等…

Flink 定义 Temporal Table 的两种方式:Temporal Table DDL 和 Temporal Table Function

博主历时三年精心创作的《大数据平台架构与原型实现&#xff1a;数据中台建设实战》一书现已由知名IT图书品牌电子工业出版社博文视点出版发行&#xff0c;点击《重磅推荐&#xff1a;建大数据平台太难了&#xff01;给我发个工程原型吧&#xff01;》了解图书详情&#xff0c;…

小程序环形进度条爬坑

在做微信小程序的时候&#xff0c;发现用canvas做的环形进度条&#xff0c;在带滚动条的view里面显示有闪动、显示不全的问题&#xff0c;后面改成echart-weixin的pie图实现了&#xff0c;option配置如下 // 表示进度的百分比 var progressValue 70;option {series: [{type: …

GC机制以及Golang的GC机制详解

要了解Golang的GC机制,就需要了解什么事GC,以及GC有哪几种实现方式 一.什么是GC 当一个电脑上的动态内存不再需要时&#xff0c;就应该予以释放&#xff0c;以让出内存&#xff0c;这种内存资源管理&#xff0c;称为垃圾回收&#xff08;Garbage Collection&#xff09;&#x…

黑马点评-短信登录业务

原理 模型如下 nginx nginx基于七层模型走的事HTTP协议&#xff0c;可以实现基于Lua直接绕开tomcat访问redis&#xff0c;也可以作为静态资源服务器&#xff0c;轻松扛下上万并发&#xff0c; 负载均衡到下游tomcat服务器&#xff0c;打散流量。 我们都知道一台4核8G的tomca…

RH850P1X芯片学习笔记-Generic Timer Module -ATOM

文章目录 ARU-connected Timer Output Module (ATOM)OverviewGLOBAL CHANNEL CONTROL BLOCK ATOM Channel architectureATOM Channel modesSOMP-Signal Output Mode PWMSOMP - ARUSOMC-Signal Output Mode CompareSOMC - ARUSOMC – COMPARE COMMANDSOMC – OUTPUT ACTIONATOM …

智慧城市中的公共服务创新:让城市生活更便捷

目录 一、引言 二、智慧城市公共服务创新的实践 1、智慧交通系统 2、智慧医疗服务 3、智慧教育系统 4、智慧能源管理 三、智慧城市公共服务创新的挑战 四、智慧城市公共服务创新的前景 五、结论 一、引言 随着信息技术的迅猛发展&#xff0c;智慧城市已成为现代城市发…

failed to connect to ‘127.0.0.1:58526‘: Connection refused

WSA使用体验 链接&#xff1a; 知乎-穿越时间一步到位&#xff0c;教你完美安装Windows 11 Android 安卓子系统 CPU不满足要求 明明是12700H&#xff0c;满足要求&#xff0c;但是应用商店说不满足&#xff0c;在设置&#xff08;注意不是控制面板的区域&#xff09;把地区改…

第二天 Kubernetes落地实践之旅

第二天 Kubernetes落地实践之旅 本章学习kubernetes的架构及工作流程&#xff0c;重点介绍如何使用Workload管理业务应用的生命周期&#xff0c;实现服务不中断的滚动更新&#xff0c;通过服务发现和集群内负载均衡来实现集群内部的服务间访问&#xff0c;并通过ingress实现外…

RabbitMQ队列

RabbitMQ队列 1、死信的概念 ​ 先从概念解释上搞清楚这个定义&#xff0c;死信&#xff0c;顾名思义就是无法被消费的消息&#xff0c;字面意思可以这样理解&#xff0c;一般来说,producer将消息投递到broker或者直接到queue里了&#xff0c;consumer 从 queue取出消息进行消…

浅析虚函数的vptr和虚函数表

浅析虚函数的vptr和虚函数表 文章目录 浅析虚函数的vptr和虚函数表前言1. 基础理论2. 实现与内部结构 前言 ​ 为了实现虚函数&#xff0c;C使用一种称为虚拟表的特殊形式的后期绑定。该虚拟表是用于解决在动态/后期绑定方式的函数调用函数的查找表。虚拟表有时会使用其他名称…

【STM32+HAL】七针OLED(SSD1306)配置(SPI版)

一、前言 关于四针OLED的I2C版配置方式&#xff0c;请转至【STM32HAL】OLED显示初始化配置 二、实现功能&#xff1a; 用SPI通信方式初始化OLED显示&#xff08;相较于I2C速度更快&#xff09; 三、方法一&#xff1a;硬件SPI通信 1、打开SPI通信&#xff08;仅传输&#xf…

互联网加竞赛 车位识别车道线检测 - python opencv

0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 深度学习 机器视觉 车位识别车道线检测 该项目较为新颖&#xff0c;适合作为竞赛课题方向&#xff0c;学长非常推荐&#xff01; &#x1f947;学长这里给一个题目综合评分(每项满分5分) …

苍穹外卖Day05——总结5

前期文章 文章标题地址苍穹外卖Day01——总结1https://lushimeng.blog.csdn.net/article/details/135466359苍穹外卖Day01——解决总结1中存在的问题https://lushimeng.blog.csdn.net/article/details/135473412苍穹外卖Day02——总结2https://lushimeng.blog.csdn.net/articl…

STM32-SPI通信协议

串行外设接口SPI&#xff08;Serial Peripheral Interface&#xff09;是由Motorola公司开发的一种通用数据总线。 在某些芯片上&#xff0c;SPI接口可以配置为支持SPI协议或者支持I2S音频协议。 SPI接口默认工作在SPI方式&#xff0c;可以通过软件把功能从SPI模式切换…

【计算机网络】HTTPS 协议原理

https 一、HTTPS 是什么二、加密1. 加密概念2. 加密的原因3. 常见的加密方式&#xff08;1&#xff09;对称加密&#xff08;2&#xff09;非对称加密 三、数据摘要(数据指纹)四、HTTPS 的工作原理探究1. 只使用对称加密2. 只使用非对称加密3. 双方都使用非对称加密4. 非对称加…

java 实现图片新增水印(动态计算水印背景 + 水印文字),附带文字乱码解决方案

文章目录 概要实现流程代码如下小结 概要 图片增加水印背景以及水印文字&#xff0c;根据文字内容是否换行&#xff0c;以及文字行高大小自适应计算背景大小 结果图如下&#xff1a; 实现流程 定义图片来源&#xff0c;以及读取字体来源(防止中文乱码)计算文字所需高度 与…

云上攻防-云原生篇KubernetesK8s安全APIKubelet未授权访问容器执行

知识点 1、云原生-K8s安全-名词架构&各攻击点 2、云原生-K8s安全-Kubelet未授权访问 3、云原生-K8s安全-API Server未授权访问 章节点&#xff1a; 云场景攻防&#xff1a;公有云&#xff0c;私有云&#xff0c;混合云&#xff0c;虚拟化集群&#xff0c;云桌面等 云厂商…