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

一、概述

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]
			
  • 25
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值