AC自动机:多模式串匹配实现敏感词过滤

文章出处:极客时间《数据结构和算法之美》-作者:王争。该系列文章是本人的学习笔记。

1 敏感词过滤场景

在很多支持用户发表内容的网站,都有敏感词过滤替换的功能。例如将一些淫秽、反动内容过滤掉,或者替换为****。在一些社交类网站为了避免做广告之嫌,会把手机号替换为*****。当然有些人为了躲避被替换会写一三七一零六捌这样的手机号。这是后话了。这篇文章我们先学习如何替换字符串类的敏感词。我们可以维护一个敏感词词典,当用户输入评论之后,通过字符串匹配算法,查找是否包含敏感词。如果有,需要找到起始位置和长度,替换为***。
  前面讲的几种字符串匹配算法:BF、RK、BM、KMP、Trie树都可以实现,但是效率不够高。我们使用多模式串匹配算法来提高效率。

2 基于单模式串和Trie树实现敏感词过滤

BF、RK、BM、KMP都是单模式串匹配算法,是在一个模式串和一个主串之间进行匹配。多模式串匹配是多个模式串和一个主串进行匹配。为了解决敏感词过滤问题,用单模式串匹配来做的话,每次匹配一个模式串和主串,匹配多次,也可以实现,只是这样多次扫描主串效率会低。

多模式串匹配算法扫描一次主串实现匹配,这样的效率就很高了。我们可以对敏感词做预处理,构建一棵Trie树。用户输入内容后,把用户输入的内容作为主串,从第一个字符C开始匹配。当匹配 到Trie树的叶子节点或者中途有不匹配的字符的时候,我们将主串的开始位置偏移一位,也就是C字符的下一位,重新在Trie树中匹配。

基于Trie树的这种匹配方式很像单模式匹配的BF算法。我们知道KMP对BF算法的改进就是引入了next数组。当匹配失败的时候,主串位置不动,模式串位置移动。基于这种思路,我们优化Trie树查找的效率,这就是AC自动机算法。

3 AC自动机算法

AC自动机算法是一种经典的多模式串匹配算法,全称是 Aho-Corasick 算法。AC=Trie树+next数组。这里的next数组是基于树的。

构建AC自动机包含两个操作:1 构建Trie树;2 在Trie树上构建失效指针。

AC自动机每个节点的结构如下。

public class AcNode {public char data; public AcNode[] children = new AcNode[26]; // 字符集只包含 a~z 这 26 个字符public boolean isEndingChar = false; // 结尾字符为 truepublic int length = -1; // 当 isEndingChar=true 时,记录模式串长度public AcNode fail; // 失败指针public AcNode(char data) {this.data = data;}
}

关于Trie树的构建看上一篇文章。这里重点描述构建失效指针。

3.1 构建失效指针

假设这里有 4 个模式串,分别是 c,bc,bcd,abcd;主串是 abcd。

我们沿着Trie树走到p节点(下图中的紫色节点),那p的失效指针就是指从根节点到p节点形成的字符串abc,跟所有模式串前缀匹配的最长可匹配后缀子串,就是箭头指向的bc子串。

  

这里解释一下最长可匹配后缀子串。abc的后缀子串有c、bc。我们拿它们与其他模式串的前缀子串去匹配。如果某个后缀子串和其他模式串的某个前缀子串可匹配,就成为可匹配后缀子串。

从可匹配后缀子串中找到最长的那个就是最长可匹配后缀子串。我们将p节点的失效指针指向那个最长可匹配后缀子串对应的前缀子串的最后一个位置。就是上图中箭头所指位置。

计算每个节点的失效指针看似复杂,是不是可以类似KMP,利用已经求得的、深度更小的节点的失效指针来推到呢。如果这样的话,我们可以逐层解决,这就是树的层次遍历的过程。

根节点的失效指针指向自己。如果已知节点p的失效指针指向q,如何推到p的子节点pc的失效指针指向什么位置。

如果q有一个子节点的字符等于pc节点的字符,那么pc的失效指针指向该节点。

如果q的所有子节点的字符都不等于pc节点的字符,那么q=q.失效指针。再继续判断。

代码如下。

public void buildFailurePointer() {Queue<AcNode> queue = new LinkedList<>();root.fail = null;queue.add(root);while (!queue.isEmpty()) {AcNode p = queue.remove();for (int i = 0; i < 26; ++i) {AcNode pc = p.children[i];if (pc == null) continue;if (p == root) {pc.fail = root;} else {AcNode q = p.fail;while (q != null) {AcNode qc = q.children[pc.data - 'a'];if (qc != null) {pc.fail = qc;break;}q = q.fail;}if (q == null) {pc.fail = root;}}queue.add(pc);}}}

最终,通过按层级计算每个节点的失效指针,最后构建完成的AC自动机如下图。

3.2 在AC自动机上匹配主串

如何在AC自动机上匹配主串呢?例如主串是a,从i=0开始,AC自动机从p=root开始。

如果p指向的子节点x的字符串等于a[i],则把p=x。这个时候我们判断一下以目前匹配的字符串来说,有哪些是匹配到的字符串(这也是失效指针的含义)。实现方式就是检测p.失效指针是否是一个模式串的结尾。如果是可以得到匹配的字符串的长度和结尾位置。继续检测p.失效指针.失效指针。结合代码看最好理解。处理完成i++。

如果p指向的子节点的字符串都不等于a[i]。则p=p.失效指针。

public void match(char[] text) { // text 是主串int n = text.length;AcNode p = root;for (int i = 0; i < n; ++i) {int idx = text[i] - 'a';while (p.children[idx] == null && p != root) {p = p.fail; // 失败指针发挥作用的地方}p = p.children[idx];if (p == null) p = root; // 如果没有匹配的,从 root 开始重新匹配AcNode tmp = p;//字符串已经匹配了一部分了,模式串中就到tmp节点。那就判断tmp是不是个字符串,如果是,那就是匹配到了。如果tmp不是个字符串,那已经匹配的这部分如果在下一位发生不匹配的时候,指针应该回退到tmp.fail。那继续看tmp.fail是不是个字符串。//如果是,那就是说已经匹配的部分包含某个字符串。while (tmp != root) { // 打印出可以匹配的模式串if (tmp.isEndingChar == true) {int pos = i-tmp.length+1;System.out.println(" 匹配起始下标 " + pos + "; 长度 " + tmp.length);}tmp = tmp.fail;}}}

3.3 AC自动机的时间复杂度

Trie 树构建的时间复杂度是 O(m*len),其中 len 表示敏感词的平均长度,m 表示敏感词的个数。

构建失效指针的时间复杂度。这里给一个不太精确的上界。假设 Trie 树中总的节点个数是 k,每个节点构建失效指针的时候,(你可以看下代码)最耗时的环节是 while 循环中的 q=q->fail,每运行一次这个语句,q 指向节点的深度都会减少 1,而树的高度最高也不会超过 len,所以每个节点构建失败指针的时间复杂度是 O(len)。整个失败指针的构建过程就是 O(k*len)。

在AC自动机上查询的时间复杂度。与构建失效指针的分析类似。最外层for循环的长度是主串的长度。循环内部耗时的操作是两个while循环,每个while循环的次数最多是len。所以时间复杂度不超过O(n*len)。一般来讲敏感词的长度不会很长,近似O(n),近似主串的长度。

从时间复杂度来讲,AC自动机和Trie树的查找性能是一样的。实际上,因为失效指针大部分指向root节点,所以绝大多数情况下,AC自动机做匹配的效率要远远高于给出的估算。只有在极端情况下才会退化为何Trie树一样的效率。
在这里插入图片描述

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

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

相关文章

[毕业论文][格式修改][摘要修改]毕业论文格式内容修改小技巧

背景 毕业季临近&#xff0c;盲审前对毕业论文进行最后一次逐行逐字修改&#xff0c;遇到的一些问题和解决办法 1.自动生成的目录行距可能不一。需要选中后单独再段落种设置行距 2.符号问题 配合查找发现问题 双引号 英文 “English” 中文“” 逗号 英文 English, 中文 &am…

第五十一期:AIOps落地关键点指南

随着越来越多企业愿意在运营中采用AIOps的模式&#xff0c;他们所要面对的问题是&#xff1a;如何以与业务需求相适应的方式来接受它。我们为您准备的一些有关AIOps落地关键点指南。 作者&#xff1a;陈峻 【51CTO.com快译】随着系统效率和复杂程度的日益提高&#xff0c;我们…

算法六——贪心

文章出处&#xff1a;极客时间《数据结构和算法之美》-作者&#xff1a;王争。该系列文章是本人的学习笔记。 1 背豆子的例子 假设我们有一个可以容纳 100kg 物品的背包&#xff0c;可以装各种物品。我们有以下 5 种豆子&#xff0c;每种豆子的总量和总价值都各不相同。为了让…

BZOJ 1827: [Usaco2010 Mar]gather 奶牛大集会 树形DP + 带权重心

Description Bessie正在计划一年一度的奶牛大集会&#xff0c;来自全国各地的奶牛将来参加这一次集会。当然&#xff0c;她会选择最方便的地点来举办这次集会。每个奶牛居住在 N(1<N<100,000) 个农场中的一个&#xff0c;这些农场由N-1条道路连接&#xff0c;并且从任意一…

第五十二期:Java开发数据库设计的14个技巧,你知道几个?

可以是一对一、一对多、多对多的关系。在一般情况下&#xff0c;它们是一对一的关系&#xff1a;即一张原始单据对应且只对应一个实体。在特殊情况下&#xff0c;它们可能是一对多或多对一的关系&#xff0c;即一张原始单证对应多个实体&#xff0c;或多张原始单证对应一个实体…

《鸟哥的Linux私房菜--基础篇》学习

第四章 显示日期与时间的指令&#xff1a;date 输入&#xff1a; (base) liyihuadeMacBook-Pro:~ liyihua$ date 输出&#xff1a; Thu Jun 6 08:44:02 CST 2019显示日历指令&#xff1a;cal 输入&#xff1a; (base) liyihuadeMacBook-Pro:~ liyihua$ cal 输出&#xff1a; J…

第五十三期:公司如何选择数据库?DynamoDB、Hadoop和MongoDB 大比拼

随着公司数据存储方式的不断扩展&#xff0c;本文旨在比较公司使用的一些更现代的数据库系统——了解DynamoDB&#xff0c;Hadoop和MongoDB可以提供哪些功能将帮助用户针对业务模型做出更好的决策。 作者&#xff1a;久谦 用户如何选择最能满足当前业务需求的数据库&#xff…

[引用格式][中文论文][毕业论文]毕业论文引用格式 英文引用文献间隔过大

英文引用文献间隔过大&#xff0c;需要选中该条英文引用文献&#xff0c;在段落设置的中文版式设置西文换行&#xff0c;设置后再进行微调 设置前 设置后 设置方法

动态规划——0-1背包问题

文章出处&#xff1a;极客时间《数据结构和算法之美》-作者&#xff1a;王争。该系列文章是本人的学习笔记。 1 0-1背包问题 背包能够承受的总重量一定w&#xff0c;每个物品的总量不同int[] weight表示。怎么放才能让背包中物品的总重量最大。 每次决定一种物品&#xff0c…

第五十四期:MongoDB与MySQL:如何选择

MongoDB和MySQL分别是领先的开源NoSQL和关系数据库。哪个最适合您的应用程序? 作者&#xff1a;XEyes行走的CODE来源 MongoDB和MySQL分别是领先的开源NoSQL和关系数据库。哪个最适合您的应用程序? 在1990年代的互联网泡沫时期&#xff0c;用于Web应用程序的一种通用软件堆栈…

动态规划——矩阵中的最短路径长度

文章出处&#xff1a;极客时间《数据结构和算法之美》-作者&#xff1a;王争。该系列文章是本人的学习笔记。 题目 假设我们有一个 n 乘以 n 的矩阵 w[n][n]。矩阵存储的都是正整数。棋子起始位置在左上角&#xff0c;终止位置在右下角。我们将棋子从左上角移动到右下角。每次…

[密码学基础][每个信息安全博士生应该知道的52件事][Bristol Cryptography][第19篇]Shamir密钥交换场景

这是一系列博客文章中最新的一篇&#xff0c;该文章列举了“每个博士生在做密码学时应该知道的52件事”:一系列问题的汇编是为了让博士生们在第一年结束时知道些什么。 Shamir密钥交换场景是一个被Adi Shamir提出的算法.算法允许多方分割一个密码,例如一个密钥.当足够多的秘密结…

第五十五期:MongoDB数据库误删后的恢复

如果部署的是 MongoDB 复制集&#xff0c;这时还有一线希望&#xff0c;可以通过 oplog 来尽可能的恢复数据;MongoDB 复制集的每一条修改操作都会记录一条 oplog&#xff1b;如果对 MongoDB 做了全量备份 增量备份&#xff0c;那么可以通过备份集及来恢复数据。 作者&#xf…

037-PHP如何返回闭包函数实例

<?php /*: 如何返回闭包函数实例*/# 直接调用将不会输出$txt的内容function demo(){$txt 我爱PHP;$func function () use ($txt) {echo $txt;};# 这里不再直接调用&#xff0c;而且是把实例返回return $func; # 区别于直接写 $func;}# 测试一下 $res demo(); // 函数返…

动态规划——莱文斯坦距离

文章出处&#xff1a;极客时间《数据结构和算法之美》-作者&#xff1a;王争。该系列文章是本人的学习笔记。 莱文斯坦距离 在搜索引擎中会有搜索词纠错的功能。这个功能背后的原理是编辑距离。 编辑距离 编辑距离是量化两个词之间的相似度。 编辑距离是指将一个字符串变为…

[密码学基础][每个信息安全博士生应该知道的52件事][Bristol Cryptography][第18篇]画一个/描述ECB,CBC,CTR模式的操作

操作模式:块密码的安全性依赖于加解密一个固定长度的明文块.当加密或者解密消息的时候,块是被需要的.我们使用一种操作模式将明文的多个块链接在一起.我们会知道,这种链接在一起的方法是十分重要. 电子密码本(ECB)模式:加密,解密. ECB模式是最直接的方法.明文被分割成m块.每一…

[密码学基础][每个信息安全博士生应该知道的52件事][Bristol Cryptography][第20篇]Merkle-Damgaard hash函数如何构造

这里讲的是MD变换,MD变换的全称为Merkle-Damgaard变换.我们平时接触的hash函数都是先构造出一个防碰撞的压缩函数.然后先证明这个小的,固定长度的压缩函数是安全的,然后再用它构造一个任意长度的哈希算法.虽然存在很多其它的构造方法,MD是迄今为止最常用的(至少是被用到最多的)…

第五十六期:IPv6只是增加了地址数量?其实真相并没有那么简单!

究竟什么是IPv6?它到底是干啥用的?IPv6的全称是Internet Protocol version 6。其中&#xff0c;Internet Protocol译为“互联网协议”。所以&#xff0c;IPv6就是互联网协议第6版。 作者&#xff1a;小枣君 10月20日&#xff0c;在乌镇举办的第六届世界互联网大会上&#x…

spring学习(10):创建项目(自动装配)

首先创建项目 pom.xml的配置文件 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://mav…

[密码学基础][每个信息安全博士生应该知道的52件事][Bristol Cryptography][第21篇]CRT算法如何提高RSA的性能?

CRT加速RSA&#xff1a;https://www.di-mgt.com.au/crt_rsa.html 转载链接&#xff1a;https://www.cnblogs.com/zhuowangy2k/p/12245513.html