1. 什么是AC自动机?

    是基于 Trie树 和 KMP失配指针 的一种高效多模式匹配算法。AC自动机能够一次构建,随后在遍历文本时同时匹配多个敏感词。

    AC自动机算法的典型应用是敏感词匹配,在各大社交媒体平台如:微博、知乎、抖音等各用户发表评论时,该算法便派上了用场,最典型的是王者荣耀,玩家在聊天框发表一些暴力,色情语言时,会被标记为'*'显示。
  2. 如果不用该算法,怎么实现敏感词的匹配?

    想到用哈希表查找的方式去实现敏感此匹配。
  3. 分析一下AC自动机和哈希表查找两种方式的优劣
  • 哈希表

    哈希表匹配 是通过将敏感词存储在哈希表中,然后逐个检查文本中的每个子串是否存在于哈希表中。每次从文本中取出一段子串,检查该子串是否在哈希表中存在。哈希表的查找时间复杂度为 (1)。

  • AC自动机

    使用Trie树存储多个敏感词,确保每个敏感词的共享前缀不会被重复存储。

    失配指针用于在匹配失败时跳转到下一个可能的匹配位置,避免重新扫描文本。

    AC自动机在搜索阶段的时间复杂度为 O(m),其中 m 是文本长度。

  1. 时间复杂度

    哈希表

    预处理时间:将敏感词插入到哈希表的时间复杂度为 O(k),其中 k 是所有敏感词的总长度。

    匹配时间:对于每个可能的子串,哈希表需要逐个验证。假设文本长度为 m,最长敏感词长度为 L,则需要 O(m⋅L) 的时间复杂度来进行逐个子串的检查。

    AC自动机

    预处理时间:构建Trie树和失配指针的时间复杂度为 O(k),与哈希表相同。

    匹配时间:AC自动机只需要遍历文本一次,每次沿着Trie树进行状态转移。匹配时间复杂度为 O(m),其中 m 是文本长度。
  2. 空间复杂度

    哈希表

    空间消耗:哈希表需要为每个敏感词存储独立的键值对,哈希表的大小与敏感词的数量成正比,因此空间复杂度为 O(n),其中 n 是敏感词的数量。

    敏感词独立存储:哈希表无法利用敏感词之间的前缀共享,因此每个敏感词都需要独立存储,容易导致重复存储较长的前缀。

    AC自动机

    空间消耗:AC自动机的Trie树利用了敏感词之间的前缀共享,减少了空间消耗。构建Trie树的空间复杂度为O(k),其中 k 是所有敏感词的总字符数。

    失配指针:失配指针需要额外的空间,但由于失配指针只会指向Trie树的其他节点,因此额外消耗不高。
  3. 匹配性能

    哈希表

    逐字符匹配:哈希表匹配的效率较低,因为它需要逐个子串进行查找。对于每一个起始位置,必须从1个字符开始,逐渐扩大到最长敏感词长度,直到发现敏感词或整个文本扫描完毕。因此,它的时间复杂度会随着文本长度的增加成倍增长。

    局限性:哈希表无法有效处理前缀相同的敏感词。例如,对于敏感词 "ab" 和 "abc",如果从文本中读到 "abc",哈希表会需要分别检查 "a"、"ab"、"abc" 三个子串。

    AC自动机

    一次性匹配:AC自动机的设计可以一次性完成多模式匹配。它在遍历文本的过程中,能够同时检测到所有敏感词的出现,且每个字符只会被遍历一次,避免了重复的子串检查。

    效率高:AC自动机的时间复杂度为 O(m),在处理大文本和大量敏感词时,匹配性能显著优于哈希表。

    代码实现:

    1.trie树的构建(前缀树)
点击查看代码
// 构建Goto表(Trie树)
void AcString::BuildGotoTable() {
for (size_t i = 0; i < patterns.size(); ++i) {
const std::string& word = patterns[i].pattern;
int current = 0; // 从根节点开始
for (char ch : word) {
if (nodes[current].sons.find(ch) == nodes[current].sons.end()) {
AddState(current, ch);
nodes[current].sons[ch] = nodes.size() - 1;
}
current = nodes[current].sons[ch];
}
// 在最后一个节点添加输出
nodes[current].output.push_back(i);
}
}

一、
构建fail指针的遍历为层次遍历
二、
root节点的fail指针指向自己本身
三、
如果当前节点父节点的fail指针指向的节点下存在与当前节点一样的子节点,则当前节点的fail指针指向该子节点,否则指向root节点。

2.Fial指针的构建

点击查看代码
void AcString::BuildFailTable() {
std::queue<int> q;
// 初始化根节点的子节点的fail指针为根节点
for (auto& pair : nodes[0].sons) {
int child = pair.second;
nodes[child].fail = 0;
q.push(child);
} // BFS遍历Trie树,构建fail指针
while (!q.empty()) {
int current = q.front();
q.pop(); for (auto& pair : nodes[current].sons) {
char ch = pair.first;
int child = pair.second; // 寻找current节点的fail指针指向的节点的子节点是否有字符ch
int fail = nodes[current].fail;
while (fail != -1 && nodes[fail].sons.find(ch) == nodes[fail].sons.end()) {
fail = nodes[fail].fail;
} if (fail == -1) {
nodes[child].fail = 0; // 回到根节点
} else {
nodes[child].fail = nodes[fail].sons[ch];
// 将fail节点的输出添加到当前节点的输出
for (int index : nodes[nodes[fail].sons[ch]].output) {
nodes[child].output.push_back(index);
//如果 fail == -1,表示当前节点没有找到合适的失败跳转路径,因此将子节点 child 的 fail 指针指向根节点 nodes[0]。
否则,将子节点 child 的 fail 指针指向 fail 节点的子节点(即 nodes[fail].sons[ch],表示可以通过 fail 节点继续匹配字符 ch)。
接着,将 fail 节点的输出模式(output)追加到子节点 child 的 output 列表中。这一步是因为 fail 节点已经匹配到了一部分模式,所以如果 child 节点也到达这个状态,它应该继承这些匹配的模式。
}
} q.push(child);
}
}
}

![](https://img2024.cnblogs.com/blog/3367816/202410/3367816-20241015143830833-326653636.png)

什么是AC自动机?如何实现?的更多相关文章

  1. 基于trie树做一个ac自动机

    基于trie树做一个ac自动机 #!/usr/bin/python # -*- coding: utf-8 -*- class Node: def __init__(self): self.value ...

  2. AC自动机-算法详解

    What's Aho-Corasick automaton? 一种多模式串匹配算法,该算法在1975年产生于贝尔实验室,是著名的多模式匹配算法之一. 简单的说,KMP用来在一篇文章中匹配一个模式串:但 ...

  3. python爬虫学习(11) —— 也写个AC自动机

    0. 写在前面 本文记录了一个AC自动机的诞生! 之前看过有人用C++写过AC自动机,也有用C#写的,还有一个用nodejs写的.. C# 逆袭--自制日刷千题的AC自动机攻克HDU OJ HDU 自 ...

  4. BZOJ 2434: [Noi2011]阿狸的打字机 [AC自动机 Fail树 树状数组 DFS序]

    2434: [Noi2011]阿狸的打字机 Time Limit: 10 Sec  Memory Limit: 256 MBSubmit: 2545  Solved: 1419[Submit][Sta ...

  5. BZOJ 3172: [Tjoi2013]单词 [AC自动机 Fail树]

    3172: [Tjoi2013]单词 Time Limit: 10 Sec  Memory Limit: 512 MBSubmit: 3198  Solved: 1532[Submit][Status ...

  6. BZOJ 1212: [HNOI2004]L语言 [AC自动机 DP]

    1212: [HNOI2004]L语言 Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 1367  Solved: 598[Submit][Status ...

  7. [AC自动机]【学习笔记】

    Keywords Search Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 131072/131072 K (Java/Others)To ...

  8. AC自动机 HDU 3065

    大概就是裸的AC自动机了 #include<stdio.h> #include<algorithm> #include<string.h> #include< ...

  9. AC自动机 HDU 2896

    n个字串 m个母串 字串在母串中出现几次 #include<stdio.h> #include<algorithm> #include<string.h> #inc ...

  10. 【BZOJ-3881】Divljak AC自动机fail树 + 树链剖分+ 树状数组 + DFS序

    3881: [Coci2015]Divljak Time Limit: 20 Sec  Memory Limit: 768 MBSubmit: 508  Solved: 158[Submit][Sta ...

随机推荐

  1. Python代码覆盖率工具之Coverage

    Python代码覆盖率工具之Coverage 在软件开发过程中,确保代码覆盖率是质量控制的关键一环.通过测量代码覆盖率,开发者可以了解哪些部分的代码正在被测试执行,哪些部分尚未被覆盖,从而优化测试策略 ...

  2. JUC并发—1.Java集合包底层源码剖析

    大纲 1.为什么要对JDK源码剖析 2.ArrayList源码一:基本原理以及优缺点 3.ArrayList源码二:核心方法的原理 4.ArrayList源码三:数组扩容以及元素拷贝 5.Linked ...

  3. 最新demo版 | 如何0-1开发支付宝小程序之小程序页面功能介绍(三)

    前两期讲了小程序开发的准备工作以及前期需要如何调试,今天我们就来介绍下开发一个支付宝小程序页面需要了解哪些信息. 一个小程序页面的整体功能的构成离不开页面展示(AXML).页面样式(ACSS)以及页面 ...

  4. Flink - [03] API

    使用scala编写flink api从不同的数据源(源端)读取数据,并进行无界流/有界流的数据处理,最终将处理好的数据sink到对应的目标端 一.maven配置 <?xml version=&q ...

  5. Python文档字符串设置--在PyCharm中

    引言 在PyCharm中,只要我们在一个函数下面输入一个三引号"""并回车,PyCharm会自动帮我们补全文档字符串,如下图所示: 然而,有些小伙伴的pycharm却无法 ...

  6. C#长短链接服务器端WebApi作映射

    [HttpGet] public IHttpActionResult GetLongLink(string code) { if (string.IsNullOrWhiteSpace(code)) { ...

  7. html5文本标签

    标题文本 h1.h2.h3.h4.h5.h6 其中 h1.h2.h3是比较常用的.h3.h4.h5.h6相对来说用的会少一点,除非结构层次比较深才会使用. 段落文本 p <p>这是一个段落 ...

  8. 【ABAQUS脚本】后处理快速出图

    效果图: # -*- coding: utf-8 -*- # Do not delete the following import lines from abaqus import * from ab ...

  9. 形态学图像处理(Morphological Image Processing)

    形态学图像处理(Morphological Image Processing) 前言 ‍ 本博客为个人总结数字图像处理一课所写,并给出适当的扩展和相应的demo. 写博客跟做 checkpoint​ ...

  10. Linux重启php-fpm

    前言 PHP-FPM 是一款简单好用的 PHP FastCGI 进程管理工具. 它可以和 Apache.Nginx 或其他服务器一起构建完整的 PHP 环境. 接下来我们看看在更改了 php.ini ...