【算法】AC自动机的优化:增量更新与删除

news/2024/5/18 13:08:10 标签: 算法, 游戏程序

一、概述

AC自动机(Aho-Corasick Automation)是著名的多模匹配算法,源于贝尔实验室,并且在实际应用中得到广泛的引用,且具有以下特点:

  • 只需要扫描一次文本,即可获取所有匹配该文本的模式串
  • 复杂度O(n)
  • 以树的结构进行存储
  • 通过Fail节点和Fail指针来提高匹配效率

对于AC自动机的具体实现,感兴趣可以自行搜索。

但是在实际应用场景中,AC自动机不仅仅只考虑匹配模式,还要考虑其模式串数据源的处理,比如模式串数据源的频繁变动(更新or移除数据),针对这样的情况下如果不断地对AC检测树进行推倒重建,在性能上消耗是十分庞大的。

因此,基于这样的场景,我们需要支持动态、快速、便捷地对已生成的AC检测树进行数据的插入、删除。

二、原理

原理很好理解,即:

  1. 新增节点自前往后遍历,删除节点自后往前遍历,逐一获取需要新增/删除的节点
  2. 更新指向该节点的Fail指针
  3. 更新该节点指向的Fail节点
  4. 在AC检测树中合并/移除该节点

在第2步中,原来的AC检测树的节点是不记录相关信息的,因此需要引入一个列表来管理该信息。

1. 新增节点

新增节点,即需要将新增加需要匹配的模式串源数据合并到已构建好的AC检测树中。

假定新增模式串为Z,已构建的AC检测树为AC_TREE

  • 判断ZAC_TREE的中最长前缀,并记录最长前缀的最后一个节点NODE
  • NODE作为遍历起点并开始遍历Z
    • 为新字符NEW_CHAR创建节点NEW_NODE并添加到NODE
    • 沿着NODE的Fail指针向上查找,直至找到某个节点下的子节点是NEW_CHAR,记录该节点的子节点为NEW_FAIL_NODE(如果没找到,则NEW_FAIL_NODE = ROOT
    • 更新NEW_NODE的Fail节点为NEW_FAIL_NODE
    • 获取Fail节点为NODE的节点列表REVER_NODE_LIST,并遍历
      • 判断REVER_NODE_LIST中节点的子节点REVER_CHILD_NODE的值是否有NEW_CHAR,如果有,则将REVER_CHILD_NODE的Fail节点设置为NEW_NODE,并更新NEW_NODEREVER_NODE_LIST
    • 设置NEW_NODE的其他属性

图文说明:

【初始状态】
在这里插入图片描述
【新增模式串后:ers】
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2. 删除节点

删除节点,即需要将某些模式串源数据已构建好的AC检测树中移除。

假定移除的模式串为Z,已构建的AC检测树为AC_TREE

  • 判断ZAC_TREE的位置,找到Z最后一个字符在AC_TREE中的节点NODE(如果不存在该模式串则跳出处理流程)
  • NODE作为遍历起点并开始反向遍历Z
    • 判断NODE是否存在其他子节点,有则跳出循环
    • 获取Fail节点为NODE的节点列表REVER_NODE_LIST,并遍历
      • REVER_NODE_LIST中的节点REVER_NODE的Fail节点设置为NODE的Fail节点
      • 更新NODEREVER_NODE_LIST
    • 更新NODE的Fail节点为NULL
    • 删除NODE,并更新NODE的父节点PARENT_NODE的子节点列表

图文说明:

【初始状态】
在这里插入图片描述

【删除模式串后:ers】
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

三、实现流程

P.S. 通过伪代码形式给出主要代码流程

// 新增节点
AddNode(list[str_1..str_n])
	for i ← 0 to n-1 do
    	str ←  list[i]; pos ← 0; node ← root
    	for j ← pos to str.len() do
    		pos ← j
    		node ← node.childNodeList[str[pos]]
   		
   		now_node ← node 
   		for j ← pos to str.len() do
   			chr ← str[j]
   			 // 查找Fail节点
   			fail_node ← GetFailNode(now_node, chr)
   			
   			// 创建新节点
   			next_node  ← CreateNewNode(now_node, chr) 
   			next_node, fail_node ← SetFailNode(next_node, fail_node)
   			
   			// 处理需要指向该节点的Fail指针
   			for k ← 0 to now_node.reverseFailNodeList do
   				tmp_node ← now_node.reverseFailNodeList[k][chr]
   				tmp_node , next_node ← SetFailNode(tmp_node , next_node)
   				now_node. reverseFailNodeList[k].childNodeList[chr] ← tmp_node 
   			

// 删除节点
DelNode(list[str_1..str_n])
	for i ← 0 to n-1 do
    	str ←  list[i]; pos ← 0; node ← root; nodeList  ← []
    	for j ← pos to str.len() do
    		pos ← j
    		nodeList.add(node)
    		node ← node.childNodeList[str[pos]]
    	
    	nodeList.reverse()
    	for j ← pos to str.len() do
    		now_node ← nodeList[j]
			fail_node ← now_node.faliNode
			// 处理指向该节点的所有Fail指针
			for k ← 0 to now_node.reverseFailNodeList do
				tmp_node ← now_node.reverseFailNodeList[k][chr]
   				tmp_node , next_node ← SetFailNode(tmp_node , next_node)
   				now_node. reverseFailNodeList[k].childNodeList[chr] ← tmp_node
   			
   			// 处理当前节点的Fail节点
  			now_node, tmpFail_node← SetFailNode(now_node, NULL) 

			// 删除该节点
			delete now_node.parentNode.childNodeList[now_node.char]
			

http://www.niftyadmin.cn/n/5430030.html

相关文章

Marin说PCB之如何利用CATIA软件把器件的3D模型导出2D格式文件

步骤一,打开CATIA软件,找到器件的3D模型文件。 如下图所示就是SD卡的3D模型图了。 若是我们想要把这个3D模型导出我们实际需要的2D的DXF或者是DWG格式的话,我们应该如何操作呢? 1,文件,新建一个DAWING格式的文件, 2,新…

Django系统权限和组的使用

目录 一、组Group的使用 1,模拟购买会员,分配组权限 2,用户组权限判断的装饰器 3,对上传和下载视图进行装饰 二、权限的使用 1,模拟购买会员,分配用户权限 2,用户权限判断的装饰器 3&…

黑客入狱知识点总结

目录 1.对称加密和非对称加密 2.什么是同源策略? 3.cookie 存在哪里?可以打开吗? 4.xss 如何盗取 cookie? 5.xss 有 cookie 一定可以无用户名密码登录吗? 6.xss 如何防御? 7.SYN 攻击原理 8.什么是…

x264编码器 API 函数介绍

x264 x264是一个开源的视频编码库,用于将视频压缩为H.264/AVC(Advanced Video Coding)格式。它是一种广泛使用的视频编码标准,能够提供高质量的视频压缩和较低的比特率。 x264库提供了一个编码器,可以将原始视频序列转换为H.264/AVC压缩的比特流。它实现了各种H.264编码算…

LeetCode题练习与总结:搜索插入位置

一、题目 给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。 请必须使用时间复杂度为 O(log n) 的算法。 二、解题思路 初始化两个指针,left 指向数组…

ETH共识升级之路

简介 根据我们之前的介绍,了解到ETH网络的共识方式,已经从 PoW 切换到了 PoS,今天我们就回顾下升级之路,以及升级带来的影响 最早的共识机制 PoW 以太坊创建之初采用了类似比特币的工作量证明机制,即矿工通过计算哈希函…

什么是分段锁?

1、典型回答 分段锁是一种将锁细化到每个段(Segment) 级别的锁设计。在 ConcurrentHashMap 中,它将整个数据结构分成多个段,每个段只锁定自己的一部分数据。每个段可以看作是一个独立的分组,只锁定该段(Segment)内部的数据操作,不…

【DataWhale学习】用免费GPU线上跑chatGLM项目实践

用免费GPU线上跑chatGLM项目实践 ​ DataWhale组织了一个线上白嫖GPU跑chatGLM与SD的项目活动,我很感兴趣就参加啦。之前就对chatGLM有所耳闻,是去年清华联合发布的开源大语言模型,可以用来打造个人知识库什么的,一直没有尝试。而…