AC算法结合双数组trie树优化关键词匹配与替换
参考文章和项目:
- Aho-Corasick算法的Java实现与分析
- 双数组Trie树(DoubleArrayTrie)Java实现
- Aho Corasick自动机结合DoubleArrayTrie极速多模式匹配
- github项目:AhoCorasickDoubleArrayTrie
trie树
trie又称前缀树、字典树;其基本性质:
- 根节点不包含字符,除根节点外的每个节点都只包含一个字符;
- 从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串;
- 每个节点的所有子节点包含的字符串不相同;
特点:
- 查找性能高;
- 很容易实现排序(每一层使用treeMap结构,中序遍历树即可排序);
- 空间换时间(如果每一层都利用hash表提升查询性能,则会出现大量哈希表,占用空间);
示意图(字符串:abb、abc、bc、bca):
基本操作:
- 插入字符串:从左到右扫描字符串,如果字符在根节点下没出现过,则插入该字符;然后沿着字典树下走一步,继续下一个字符,重复操作直到字符串结尾,则记录emits;
- 搜索字符串:从左到右扫描字符串,沿着字典树往下找,若找到了,则继续,直到扫描结束(匹配成功)或者没找到(匹配失败);
Aho-Corasick算法
Aho-Corasick算法简称AC算法,其思想是按照文本的字符顺序,接收字符并发生状态转移,这些状态缓存了转移成功且为模式串结尾、转移成功但不是模式串结尾、转移失败等三种情况下的跳转与输出情况,从而降低了匹配操作的时间复杂度至O(n);
算法基本结构:
- success:也称goto表(实际上就是trie树),代表匹配成功时转移到下一个状态;
- failure:无法按照success跳转时,则按照failure跳转至下一个状态;
- emits:也称output表,代表命中模式串(可以同时命中多个);
用法举例(模式串:he、she、his、hers):
构建自动机(实线代表success,虚线代表failure,红色节点代表emits):

匹配过程(输入文本:ushers):
- u:无节点匹配,继续;
- s:按success表跳转至节点3;
- h:按success表跳转至节点4;
- e:按success表跳转至节点5,emits命中she、he;
- r:按seccess表跳转失败,按failure表跳转至节点8;
- s:按success表跳转至节点9,emits命中hers,输入结束;
构建:
- success表和emits实际上就是trie树,按照trie树的插入规则插入数据即可构建;
- failure表:
- 规定深度为1的节点的fail值都是根节点;
- 按照树的深度顺序依次计算各个节点的fail值;
- 计算规则为:假设当前状态是s1,s1的前一状态时s0,那么有:
s1=s0.success(c);则s2=s0.failure.success(c),如果s2为空,则s3=s0.failure.failure.success(c),依次下去,直到sn不为空或者sn为根节点,则s1.failure=sn
failure表构建示例(以上文中的自动机示意图为例):
- 节点1、3的fail值为根节点;
- 节点2:
failure=1.failure=0; - 节点6:
failure=1.failure=0; - 节点4:
failure=3.failure.success('h')=1; - 节点8:
failure=2.failure=0; - 节点7:
failure=6.failure.success('s')=3; - 节点5:
failure=4.failure.success('e')=2; - 节点9:
failure=8.failure.success('s')=3;
双数组trie树
参考文章:双数组Trie树(DoubleArrayTrie)Java实现
双数组trie树(DoubleArrayTrie)是一种空间复杂度较低的trie树,应用于字符区间大的语言(如:中文),是trie树结构的压缩形式,仅用两个线性的数组来表示trie树,该结构有效结合了数字搜索树检索时间高效的特点和链式表示的trie树空间结构紧凑的特点;双数组trie的本质是一个确定有限状态的自动机,每个节点代表一个状态,根据变量不同进行状态转换,当到达结束状态或者无法转移时,完成一次查询操作;在双数组所有键中包含的字符串之间的联系都通过简单的数学加法运算表示,这样不仅提高了检索速度,也省去了链式结构中大量的指针,节省空间;
基本原则(s代表状态,c代表输入字符的编码):
- root节点:base[0] = 1;check[0] = 0
- base[s1] + c1 = s2;
- base[s2] + c2 = s3;
- 当base[sn] < 0时,代表字符串结束;
- check[s2] = base[s1];
每个节点的插入过程实际就是维护base和check两个数组,base用于存储当前状态和状态转移,check用于验证是否由同一个状态转移而来,即该状态的上一个状态是否正确,base小于0,则代表字符串结束;而维护这两个数组的核心就是,找到一个begin值使得check[begin]+check[begin+1]+...+check[begin+n]==0;也就是找到一段连续的空闲空间用于存放每个节点字符对应的code;从而实现快速定位字符的同时压缩trie树的空间结构;
使用原理
参考文章:Aho Corasick自动机结合DoubleArrayTrie极速多模式匹配
AC算法结合双数组字典树匹配过程:
- 遍历输入字符串(c为字符,sn为状态,n从0开始,s0=0),
- 先按success跳转:
s(n+1)=base[sn] + c + 1,若check[s(n+1)]=base[sn],则跳转成功,返回s(n+1),若跳转失败,则判断s0==0?是则返回0,否则继续按failure跳转; - 按failure跳转,
s(n+1)=base[fail[sn]] + c + 1,若check[s(n+1)]=base[sn],则跳转成功,返回s(n+1),若跳转失败,则判断s0==0?是则返回0,否则继续按failure跳转; - 根据上面的返回值,验证output表,若
output[s(n+1)] != null,代表匹配成功,返回所有匹配的模式串; - 继续下一个字符的处理;
应用
本次需求:关键字替换,要求将文本中出现的关键词替换为指定链接,且重复出现的关键词只替换第一次,同时,若某个关键词a是另一个关键词b的一部分,那么如果文本匹配b时,则不匹配a;
代码实现(基于github项目:AhoCorasickDoubleArrayTrie的AhoCorasickDoubleArrayTrie类提供的功能做扩展):
扩展build方法
根据trie树匹配过程可以知道,同一个节点对应的output表输出可能会对应多个模式串,根据本次需求,需要优先匹配较长的模式串,因此需要扩展build方法,对output表的模式串按长度排序;
@Override
public void build(Map<String, V> map) {
super.build(map);
for (int[] out : this.output) {
if (out != null && out.length > 1) {
ArrayList<Integer> list = new ArrayList<>(out.length);
for (int value : out) {
list.add(value);
}
list.sort(Comparator.comparingInt(e -> l[e]));
for (int i = 0; i < list

本文详细介绍了Aho-Corasick(Aho-Corasick Algorithm, AC算法)和双数组Trie树(DoubleArrayTrie)在关键词匹配与替换中的优化方法。AC算法结合双数组Trie树能实现高效的文本匹配,并在关键词替换过程中处理重复和部分匹配的问题,确保最长匹配优先。此外,还提供了具体的代码实现,包括扩展的build方法和匹配方法,以适应特定需求,如只替换首次出现的关键词。
最低0.47元/天 解锁文章
682

被折叠的 条评论
为什么被折叠?



