龙空技术网

AC自动机增量更新算法

杰哥爱编程 36

前言:

此时兄弟们对“什么叫增量更新”大约比较着重,看官们都需要知道一些“什么叫增量更新”的相关内容。那么小编在网络上收集了一些关于“什么叫增量更新””的相关资讯,希望大家能喜欢,朋友们快快来学习一下吧!

AC自动机算法概述

Aho-Corasick算法[1]是多模式匹配中的经典算法,目前在实际应用中较多。Aho-Corasick算法通过将模式串预处理为确定有限状态自动机,这个数据结构是Aho-Corasick自动机,简称AC自动机。模式匹配的时候,只需要扫描文本一遍就能得到所有匹配该文本的模式串,其时间复杂度为O(n),即只跟输入文本长度线性相关,与模式串的数量和长度无关。

在Aho-Corasick算法的原始论文中,描述了自动机构建的过程,包含两个步骤:(1)、首先扫描所有的模式串,构建goto状态转换函数,在实际的程序实现中goto函数实际上是一颗Trie树结构;(2)、利用上一步构建的Trie树,构建failure函数。在模式匹配的过程中,一个个字符的方式扫描待匹配的输入文本,在Trie树中从根节点进行搜索,一旦遇到字符匹配失败的节点,就跳转到failure函数指向的下一个节点继续匹配,直到输入文本扫描完毕,得到匹配的所有模式串。因为failure函数的构建使得匹配失败时可以利用之前匹配的结果,无需从头开始匹配,因此匹配的性能比较好。在实际的应用中,模式串数据库可能量非常巨大,而且会频繁的变化,例如对于网络爬虫的URL黑白名单会频繁更新,在匹配的同时,需要不断重构自动机。显然,一种可行的方案是依照作者在论文中介绍的那样,每次模式串更新,就对自动机从0开始重建,这种方案对于模式库比较小的场景下是可行的,但是模式库数量比较大的时候就不可能,每次重建的性能消耗会比较大。如果能有一种算法能增量式地进行构建,而不是每次推倒重来,将会极大减少模式库更新的性能消耗,也更加符合实际应用场景。

针对AC自动机的动态更新模式库的需求,这里介绍一种增量更新的算法,不需要每次重建自动机索引树,而只是在一个局部范围内微调这颗树,使得自动机的正确性保持不变。

接下来描述算法详细过程。

基于AC自动机原始算法,我们增加两个增量添加模式和增量删除模式的算法。

模式添加算法

模式串添加算法见Algorithm-1,整个算法分为两个步骤:首先利用Trie树找到需要添加的模式串在Trie树中匹配最长前缀串的节点位置,然后根据需要对Trie树进行节点扩展,以及failure函数的局部调整。1-6行是对输入模式k1,k2,…,km一个匹配的过程,找出最长前缀匹配。这里存在两种情况,第一种情况,这个模式串能被Trie树完整匹配,此时Trie树的结构不需要扩展,也就是不需要创建新节点,算法可以提前完成(也就是对应j>m,7-25行的代码直接被跳过);第二种情况,模式串被部分匹配,这时需要插入新的树节点,对应7-25行的过程。

Algorithm 1. add pattern incrementallyInput: pattern K=k1,k2,...,km, goto function G, output O, failure function F, reverse failure function RF, maximun state number maxstateOutput: adjusted goto function G, adjusted output O, adjusted failure function F, adjusted reverse failure function RF, adjusted maximun state number maxstateMethod.begin1	state<--0; j<--1;2	while G(state, kj) != fail  and j <= m do3	begin4		 state <--G(state, kj);5		 j <-- j + 1;6	end7	for p<-- j until m do8	begin9		  newstate <-- maxstate + 1; 10		maxstate <-- maxstate + 1;11		failure <-- findFailure(state, kp, G, F);12		G(state, kp) <-- newstate;13		F(newstate) <-- failure;14		RF(failure) <-- RF(failure) + {newstate}15		for all s in RF(state) do16		begin17			if G(s, kp) != fail  then do18			begin19				RF(F(s)) <-- RF(F(s)) - {s};20				F(s) <-- newstate;21				RF(newstate) <-- RF(newstate) + {s};22			end23		end24	       state <-- newstate;25	 end26	 O(state)<--K;end

7-25行遍历剩下的后缀串的每一个字符kp,对每一个字符都需要创建一个新的Trie树节点(状态),然后需要调整failure函数。调整failure函数也可以分解成两个步骤:1. 调整newstate的failure指针;2. 调整其他节点的应该指向newstate的failure指针。

9-14行首先创建一个新的节点newstate插入树中。然后设置newstate的failure指针。寻找failure指针指向节点的算法参见Algorithm-2,原则是沿着newstate的父亲节点state的failure指针链搜索,一直搜索到具有G(s, kp)为非空的节点为止,该节点就是newstate的failure指向节点。最坏情况,state的failure指针链一直会到达根节点会结束,因为根节点的G(s, kp)肯定非空。

Algorithm 2. findFailureInput: state s, character k, goto function G, failure function FOutput: failure state fMethodbegin1  do2	 begin3		  state <-- F(s);4		  next <-- G(state, k);5	 end6	 while next == fail 7	   f <-- next;end

设置完newstate的failure指针之后,还需要考虑现存的Trie树中,哪些节点的failure指针需要指向这个新建的newstate。如何寻找这些候选节点呢,实际上,这些需要调整failure指针的候选节点,其父节点的failure指针必然指向newstate的父节点state。我们只需要遍历failure指向state的节点,记为RF(state),判断它们是否存在以字符kp为跳转的子节点G(s,kp),如果G(s,kp)存在,则把该子节点G(s,kp)的failure指针指向newstate。注意,此时,newstate所代表的字符串是是候选节点G(s,kp)所代表的字符串的最长后缀。

最终,调整完failure函数之后,设置最后一个叶子节点state的output,算法结束。

为了便于理解,这里给出一个案例,刚开始的时候,自动机结构如图1的初始状态,假设我们添加模式”ers”,因为”ers”没有任何一个前缀能匹配到的Trie树中,因此算法进入7-23行,创建紫色的新节点10,并设置它的failure指针,指向root,当执行完后就变成“第一步”所示状态。然后设置failure应该指向节点10的failure指针。我们知道10的父节点是0, 根据算法,failure指向0的节点是1、2、3、6、8,但是具有字符e的儿子节点的只有节点1,因此1的e边指向孩子节点2的failure指针需要调整成指向10,见图示的“第二步”,以此类推,当e、r、s三个字符循环构建完成之后,最终自动机达到图示的“最终状态”。

图1-添加模式”ers”的过程示例图

介绍完了模式添加算法,接下来我们介绍模式删除算法

模式删除算法

删除算法基本上是添加算法的逆过程,同样,先找到模式串的完全匹配节点在Trie树种的节点位置,如果模式串不能被Trie树完全匹配,也就是匹配失败,则说明模式串本身不存在,删除提前结束。同时,如果删除模式串之后,不改变树的结构,则提前终止操作,见Algorithm-3中的第10行以及25行。

Algorithm 3. remove pattern incrementallyInput: pattern K=k[1],k[2],...,k[m], goto function G, output O, failure function F, reverse failure function RFOutput: adjusted goto function G, adjusted output O, adjusted failure function F, adjusted reverse failure function RFMethod.begin1	state<--0; j<--1; list <-- empty;2	while G(state, kj) != fail  and j <= m do3	begin4		list <-- list + {state};5		state <--G(state, kj);6		j <-- j + 1;7	end8	if j > m then return;9	O(state)<--null;10	if for any character c that G(state, c)=fail then return; 11	list <-- list + {state};12	for p<--m+1 until 1 do13	begin14		current <-- list[p];15		prev <-- list[p-1];16		for all s in RF(current) do17		begin18			if G(s, kp) != fail  then do19				F(s) <-- F(current);20				RF(F(current)) <-- RF(F(current)) + {s};21			endif22		end23	      RF(F(current)) <-- RF(F(current)) - {current};24	      G(prev, k[p-1]) <-- fail;25		if there exists a character c that G(prev, c) != fail then return;26	endend

至此,AC自动机增量添加和删除模式介绍完了,这里给出一个完整的实现代码:

[1] Aho, Alfred V.; Corasick, Margaret J. (June 1975). "Efficient string matching: An aid to bibliographic search". Communications of the ACM. 18 (6): 333-340. doi:10.1145/360825.360855. MR 0371172.

标签: #什么叫增量更新 #ac算法匹配url #ac自动机算法代码 #ac算法改进