一、概述
AC自动机(Aho-Corasick Automation)是著名的多模匹配算法,源于贝尔实验室,并且在实际应用中得到广泛的引用,且具有以下特点:
- 只需要扫描一次文本,即可获取所有匹配该文本的模式串
- 复杂度O(n)
- 以树的结构进行存储
- 通过Fail节点和Fail指针来提高匹配效率
对于AC自动机的具体实现,感兴趣可以自行搜索。
但是在实际应用场景中,AC自动机不仅仅只考虑匹配模式,还要考虑其模式串数据源的处理,比如模式串数据源的频繁变动(更新or移除数据),针对这样的情况下如果不断地对AC检测树进行推倒重建,在性能上消耗是十分庞大的。
因此,基于这样的场景,我们需要支持动态、快速、便捷地对已生成的AC检测树进行数据的插入、删除。
二、原理
原理很好理解,即:
- 新增节点自前往后遍历,删除节点自后往前遍历,逐一获取需要新增/删除的节点
- 更新指向该节点的Fail指针
- 更新该节点指向的Fail节点
- 在AC检测树中合并/移除该节点
在第2步中,原来的AC检测树的节点是不记录相关信息的,因此需要引入一个列表来管理该信息。
1. 新增节点
新增节点,即需要将新增加需要匹配的模式串源数据合并到已构建好的AC检测树中。
假定新增模式串为Z,已构建的AC检测树为AC_TREE:
- 判断Z在AC_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_NODE的REVER_NODE_LIST
- 设置NEW_NODE的其他属性
图文说明:
【初始状态】
【新增模式串后:ers】
2. 删除节点
删除节点,即需要将某些模式串源数据已构建好的AC检测树中移除。
假定移除的模式串为Z,已构建的AC检测树为AC_TREE:
- 判断Z在AC_TREE的位置,找到Z最后一个字符在AC_TREE中的节点NODE(如果不存在该模式串则跳出处理流程)
- 以NODE作为遍历起点并开始反向遍历Z
- 判断NODE是否存在其他子节点,有则跳出循环
- 获取Fail节点为NODE的节点列表REVER_NODE_LIST,并遍历
- 将REVER_NODE_LIST中的节点REVER_NODE的Fail节点设置为NODE的Fail节点
- 更新NODE的REVER_NODE_LIST
- 更新NODE的Fail节点为NULL
- 删除NODE,并更新NODE的父节点PARENT_NODE的子节点列表
图文说明:
【初始状态】
【删除模式串后:ers】
三、实现流程
P.S. 通过伪代码形式给出主要代码流程
// 新增节点
AddNode(list[str_1..str_n])for i ← 0 to n-1 dostr ← list[i]; pos ← 0; node ← rootfor j ← pos to str.len() dopos ← jnode ← node.childNodeList[str[pos]]now_node ← node for j ← pos to str.len() dochr ← 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 dotmp_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 dostr ← list[i]; pos ← 0; node ← root; nodeList ← []for j ← pos to str.len() dopos ← jnodeList.add(node)node ← node.childNodeList[str[pos]]nodeList.reverse()for j ← pos to str.len() donow_node ← nodeList[j]fail_node ← now_node.faliNode// 处理指向该节点的所有Fail指针for k ← 0 to now_node.reverseFailNodeList dotmp_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]