基于双数组trie树的AC自动机(含JAVA代码讲解)

Java Aho Corasick 自动机的使用 AC 自动机是一种用于字符串多模式匹配的算法,其算法实现的底层数据结构多为字典树,其中一种实现的具体信息读者可参考Aho Corasick 自动机结合 DoubleArrayTrie 极速多模式匹配。在实际开发中 AC 自动机常用于关键词识别提取的场景,以下是相关使用示例。 阅读详情

基于双数组trie树的AC自动机

前面我们已经介绍过 AC自动机 ,但在实际使用当中如果需要构建的词典树特别大,原始版本的AC自动机在做查询时耗时会比较多,而基于双数组trie树的AC自动机恰好能够弥补这一缺陷。

下面我们将基于hankcs实现的 AhoCorasickDoubleArrayTrie 代码来讲解双数组trie树的AC自动机的构建以及查询过程。

构建双数组trie树AC自动机

双数组trie树AC自动机的构建过程分为三步:

  1. 构建trie树
  2. 根据trie树构建双数组
  3. 构建AC自动机所需的fail表和output表
public void build(Map<String, V> map)
        {
            // 把值保存下来
            v = (V[]) map.values().toArray();
            l = new int[v.length];
            Set<String> keySet = map.keySet();
            // 构建二分trie树
            addAllKeyword(keySet);
            // 在二分trie树的基础上构建双数组trie树
            buildDoubleArrayTrie(keySet.size());
            used = null;
            // 构建failure表并且合并output表
            constructFailureStates();
            rootState = null;
            loseWeight();
        }

构建trie树

这里 addAllKeyword(keySet) 构建trie树跟 AC自动机 中的一样,需要在尾节点处的emits里添加keystring,但由于双数组trie树AC自动机只保存数组,不储存树结构,因此这里 数组v内储存所有的词,而emits中添加的是keyword在数组v里的index

private void addKeyword(String keyword, int index)
        {
            State currentState = this.rootState;
            for (Character character : keyword.toCharArray())
            {
                currentState = currentState.addState(character);//trie树添加节点
            }
            currentState.addEmit(index);//尾节点处添加词的index到emits
            l[index] = keyword.length();
        }

构建双数组

  • 参见 双数组trie树 中的介绍,首先利用一个outer循环,找到一个begin,使得满足对于当前节点tCurrent和它的所有子节点siblings,有:
  1. begin = base[tCurrent]
  2. 对于所有子节点sibling: 位置 begin + code(sibling) 都没用被占用
outer:
            while (true)
            {
                pos++;//每当有一个sibling的位置 begin + code(sibling) 已经被占用,pos就加一

                if (allocSize <= pos)
                    resize(pos + 1);

                if (check[pos] != 0)
                {
                    nonzero_num++;
                    continue;
                }
                else if (first == 0)
                {
                    nextCheckPos = pos;
                    first = 1;
                }

                begin = pos - siblings.get(0).getKey(); //这里begin用来记录此时tCurrent的base值
                if (allocSize <= (begin + siblings.get(siblings.size() - 1).getKey()))
                {
                    // progress can be zero // 防止progress产生除零错误
                    double toSize = Math.max(1.05, 1.0 * keySize / (progress + 1)) * allocSize;
                    int maxSize = (int) (Integer.MAX_VALUE * 0.95);
                    if (allocSize >= maxSize) throw new RuntimeException("Double array trie is too big.");
                    else resize((int) Math.min(toSize, maxSize));
                }

                if (used[begin])
                    continue;

                for (int i = 1; i < siblings.size(); i++)
                    if (check[begin + siblings.get(i).getKey()] != 0)//说明位置begin + siblings.get(i).getKey()已经被占用
                        continue outer;

                break;
            }

Note:代码中利用 check[begin + siblings.get(i).getKey()] 是否为零来判断位置 begin + code(sibling)是否已经被占用,若不为零,则说明该位置已经被占用

  • 找到父节点tCurrent的base值之后,就可以将所有子节点siblings的check值设为base[tCurrent]:
for (Map.Entry<Integer, State> sibling : siblings)
     {
          check[begin + sibling.getKey()] = begin;
     }

尽管 双数组trie树 中介绍的是 check[child_index] = father_index ,但由于base是一个单射,所以这里可以直接将check[child_index] 设为 base[father_index]

  • 接下来分两种情况处理子节点siblings:
  1. 如果sibling是尾节点,它的base值可以直接继承父节点的base值
  2. 如果sibling不是尾节点,那么它就要添加到siblingQueue中,重复上述过程,通过检验它的子节点index是否冲突才能确认该节点的base值
            for (Map.Entry<Integer, State> sibling : siblings)
            {
                List<Map.Entry<Integer, State>> new_siblings = new ArrayList<Map.Entry<Integer, State>>(sibling.getValue().getSuccess().entrySet().size() + 1);

                if (fetch(sibling.getValue(), new_siblings) == 0) // 表示当前子节点sibling是叶子节点
                {
                    base[begin + sibling.getKey()] = (-sibling.getValue().getLargestValueId() - 1);
                    progress++;
                }
                else //对于还有子节点的sibling来说,就要将其加入siblingQueue,然后重复上面的循环
                {
                    siblingQueue.add(new AbstractMap.SimpleEntry<Integer, List<Map.Entry<Integer, State>>>(begin + sibling.getKey(), new_siblings));
                }
                sibling.getValue().setIndex(begin + sibling.getKey());//sibling的index是可以确定的
            }

Note:不管是否是尾节点,tCurrent所有的siblings的index都是知道的了。所以最后都有 sibling.getValue().setIndex(begin + sibling.getKey());

  • 最后一步就是将tCurrent的base值设为begin:
	Integer parentBaseIndex = tCurrent.getKey();
	if (parentBaseIndex != null)
	{
    	base[parentBaseIndex] = begin;
	}

构建fail和output

fail node的寻找过程跟 AC自动机 中是一样的,唯一的区别是这里的fail和output都需要做成数组。

  • fail数组:

假设 index 为 i 的节点,其 fail node 的 index 为 j ,那么有 :
f a i l [ i ] = j fail[i] = j fail[i]=j

    public void setFailure(State failState, int fail[])
    {
        this.failure = failState;
        fail[index] = failState.index;
    }
  • output数组:

output数组是用来放每个位置节点的所有emits的:
o u t p u t [ S t a t e . i n d e x ] = S t a t e . e m i t s output[State.index] = State.emits output[State.index]=State.emits
由于 State.emits 本身是个一维数组,所以output是一个二维数组。

        private void constructOutput(State targetState)
        {
            Collection<Integer> emit = targetState.emit();
            if (emit == null || emit.size() == 0) return;
            int[] output = new int[emit.size()];
            Iterator<Integer> it = emit.iterator();
            for (int i = 0; i < output.length; ++i)
            {
                output[i] = it.next();
            }
            AhoCorasickDoubleArrayTrie.this.output[targetState.getIndex()] = output;
        }


双数组trie树AC自动机的查询

  • text是要查询的文本,假设现在指针已经走到了节点currentState,text文本也已经走到了位置i,那么首先用getState()查找currentState的子节点中是否有字text(i),如果有,则返回这个节点的index;如果子节点中没有该字,那么就从currentState的fail node的子节点中找并返回:
    private int getState(int currentState, char character)
    {
        int newCurrentState = transitionWithRoot(currentState, character);  // 先按success跳转
        while (newCurrentState == -1) // 跳转失败的话,按failure跳转
        {
            currentState = fail[currentState];
            newCurrentState = transitionWithRoot(currentState, character);
        }
        return newCurrentState;
    }
  • 但是由于现在我们储存的是数组而不是trie树,所以查找currentState的子节点中是否有字符c,首先要根据 base[currentState] + code(c) 找到currentState经字符c转移到的位置,接下来要通过检查 check[base[currentState] + code(c)] = base[currentState]是否成立来判断转移的正确性。如果转移正确,则返回转移到达的位置 base[currentState] + code(c)
    protected int transitionWithRoot(int nodePos, char c)
    {
        int b = base[nodePos];
        int p;

        p = b + c + 1;// p是nodePos处的节点按字符c转移到的节点的index
        if (b != check[p])//检查根据c找到的节点,其父节点是否是nodePos位置上的节点
        {
            if (nodePos == 0) return 0;
            return -1;
        }

        return p;
    }
  • 有了转移到的节点的index之后,就可以利用ouput[]数组索引到该节点的emits,这里的emits里面又是词语的索引,这里就可以用储存词语的数组v[]还原text文本hit到的词:
    private void storeEmits(int position, int currentState, List<Hit<V>> collectedEmits)
    {
        int[] hitArray = output[currentState];//利用output索引到currentState的emits数组
        if (hitArray != null)
        {
            for (int hit : hitArray)
            {
                collectedEmits.add(new Hit<V>(position - l[hit], position, v[hit]));//利用数组v还原词语
            }
        }
    }

从KMP到AC自动机:用Java实现多模式匹配的进阶之路 本文深入探讨了从KMP算法到AC自动机(Aho-Corasick)的多模式匹配进阶之路,详细介绍了AC自动机的原理与Java实现。通过Trie树和失败指针的设计,AC自动机能够高效处理大规模模式串匹配,适用于敏感词过滤等场景。文章包含完整的Java代码示例和性能优化技巧,帮助开发者掌握这一强大的字符串匹配技术。 阅读详情

相关推荐

双数组Trie树高效构建有向无环图

图 图是很常见的一种结构了,不管是数据结构算法中的各种图结构,还是机器学习中的概率图。图主要是由若干顶点及连接两顶点的边所构成的图形,通过它可以用来描述某些事物之间的某种特定关系。 有向无环图 有向无环图,即 Directed Acyclic Graph,属于有向图,图结构中不存在环,可用来表示事件之间依赖关系。 Trie树 Trie 是一种搜索树,它的 key 都为字符串,...

seaboat——a free boat on the sea.(公众号:远洋号) 1657

自然语言处理hanlp------9基于双数组字典树的AC自动机

文章目录前言一、pandas是什么?二、使用步骤1.引入库2.读入数据总结 前言 提示:这里可以添加本文要记录的大概内容: 例如:随着人工智能的不断发展,机器学习这门技术也越来越重要,很多人都开启了学习机器学习,本文就介绍了机器学习的基础内容。 提示:以下是本篇文章正文内容,下面案例可供参考 一、pandas是什么? 示例:pandas 是基于NumPy 的一种工具,该工具是为了解决数据分析任务而创建的。 二、使用步骤 1.引入库 代码如下(示例): import numpy as np import

iterhuiの博客 1798

保姆级教程:在RK3568上搞定PR2100K+GC2385双摄(从DTS配置到HAL层补丁)

本文详细介绍了在RK3568平台上调试PR2100K+GC2385双摄系统的完整流程,从DTS配置到HAL层补丁。涵盖硬件连接、媒体控制器拓扑验证、Camera3配置、HAL层特殊补丁及性能优化等关键步骤,帮助开发者快速解决双摄调试中的常见问题,提升嵌入式视觉系统开发效率。

weixin_28724661的博客 195

关于双数组字典树ac自动机递归版实现的理解

关于双数组字典树ac自动机递归版实现的理解 字典树 简介: 字典树又名单词查找树是一种结构简单好用的树型结构:电影应用是用于统计,排序和保存大量字符串,优点是利用字符串公共祖先减少查询时间,减少无所谓的字符串比较 树形结构 树形结构的点代表的是集合,边代表的是关系,而在字典树中,字符对应的是边,节点则是链接上一个字符和这个字符的关系,是字符构成单词的关键.如图所示 ) 结构定义 #define B...

teslali 1260

2.1 SRv6 TE Policy

SRv6 TE Policy是在SRv6技术基础上发展的一种新的隧道引流技术。SRv6 TE Policy路径表示为指定路径的段列表(Segment List),称为SID列表(Segment ID List)。每个SID列表是从源到目的地的端到端路径,并指示网络中的设备遵循指定的路径,而不是遵循IGP计算的最短路径。如果数据包被导入SRv6 TE Policy中,SID列表由头端添加到数据包上,网络的其余设备执行SID列表中嵌入的指令。

m0_70976024的博客 1237

MTK MT6833平台屏幕与TP驱动移植实战:从FAE伪代码到内核模块集成

本文详细介绍了在MTK MT6833平台上进行屏幕与TP驱动移植的完整实战流程。文章从解析供应商提供的初始化伪代码入手,逐步拆解了在LK阶段点亮屏幕、在内核层集成Panel驱动以支持休眠唤醒,以及移植并调试sitronix_ts触摸驱动的核心步骤与关键技巧,为嵌入式开发人员提供了从零到一集成新硬件的清晰指南。

rnn9storyteller的博客 441

基于双数组的AC匹配算法学习

基于双数组的AC匹配算法分析0. 前言阅读本文之前,你需要了解KMP算法的原理以及自动机的相关概念。1 AC算法1.1 AC算法简述AC算法是基于有限状态自动机算法,在进行串匹配之前,先对模式串集合进行预处理,得到树形有限自动机,然后只需对文本进行一次扫描,便可以找到所有匹配成功的模式串。 例如以模式串集合P{she, he, her, him, hers, his}为例,构建树形状态转移自动机。

choumin的专栏 4344

Matplotlib图片尺寸调整全攻略:从figsize到savefig的实战技巧

在数据可视化与科学计算中,图形尺寸控制是影响图表可读性与专业度的基础环节。其核心原理在于协调逻辑尺寸(英寸)与物理输出(像素)的映射关系,这直接决定了信息呈现的清晰度与布局合理性。从技术价值看,精准的尺寸控制能提升图表在不同媒介(如学术论文、报告、网页)中的适配性,避免显示不全、模糊失真等常见问题。在应用场景上,无论是Jupyter Notebook中的交互探索、自动化报告的批量生成,还是满足严格出版要求的插图制作,都需要掌握对应的尺寸调整策略。本文聚焦于matplotlib中的figsize参数、rcPa

weixin_30899023的博客 328

AC 自动机的实现(使用双数组 DoubleArray Trie 树)

关于,有太多的文章在讲述它的原理,讲述者借此来展示自己的算法能力。但其实AC自动机的原理很简单,真正困难的地方在于一个高效的!

Gibson314的博客 569

在 ABAP Push Channel 里玩转 Detached Client:用 Attach Handle 把 WebSocket 连接交接给业务逻辑

摘要:本文介绍了ABAP Push Channels (APC)中的Detached Client技术,该技术通过将WebSocket连接建立与后续处理分离,实现更高效的实时通信。核心流程包括创建连接、使用connect_and_detach交接连接、以及通过attach handle重新附着发送消息。文章还探讨了PCP协议和AMC技术在企业级推送场景中的应用,并通过示例代码展示了Detached Client的具体实现方式。这种设计解决了传统轮询的资源浪费问题,特别适用于生产线监控、客服系统等需要服务端主

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 2313

数学建模Matlab算法,第七章 对策论

对策论作为研究竞争与决策的数学理论,从零和对策到非常数和对策,为分析各类博弈问题提供了系统框架。其核心要素包括局中人、策略集和赢得函数,通过稳定解、Nash平衡点等概念揭示竞争规律。该理论广泛应用于经济、军事、政治等领域,不仅提供具体分析工具,更改变了决策思维方式,强调在互动中实现理性选择。对策论既揭示了竞争本质,也凸显合作价值,为解决社会矛盾、促进共赢发展提供了重要启示,在全球化背景下将发挥更大作用。

unber的博客 107

AC算法结合双数组trie树优化关键词匹配与替换

AC算法结合双数组trie树优化关键词匹配与替换 参考文章和项目: Aho-Corasick算法的Java实现与分析 双数组Trie树(DoubleArrayTrie)Java实现 Aho Corasick自动机结合DoubleArrayTrie极速多模式匹配 github项目:AhoCorasickDoubleArrayTrie 目录AC算法结合双数组trie树优化关键词匹配与替换trie树Aho-Corasick算法双数组trie树使用原理应用扩展build方法扩展匹配方法使用 trie树 trie

nzgry_csdn的博客 1786

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

AC 自动机算法 JAVA代码 实现

AC自动机算法 1、简要概述 ​ 什么是AC自动机算法? ​ AC自动机算法是一种多模式字符串匹配算法,什么是多模式?从单模式说起,举个kmp算法的例子,单模式就是利用kmp算法验证某个敏感词在一个字符串内是否存在;多模式是说验证多个敏感词在一个字符串内是否存在。如果使用kmp来做的话,依次遍历多个敏感词,每个敏感词使用kmp验证,当敏感词数量很多的时候效率很低,这时候就用到AC自动机算法了。 ​ 为什么AC自动机算法效率高? ​ 结论:其能在约为O(n) 的时间复杂度内完成对多个敏感词的查找,而且

dream_coke的博客 3468

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

java 有向无环图 树_双数组Trie树高效构建有向无环图

图图是很常见的一种结构了,不管是数据结构算法中的各种图结构,还是机器学习中的概率图。图主要是由若干顶点及连接两顶点的边所构成的图形,通过它可以用来描述某些事物之间的某种特定关系。有向无环图有向无环图,即 Directed Acyclic Graph,属于有向图,图结构中不存在环,可用来表示事件之间依赖关系。Trie树Trie 是一种搜索树,它的 key 都为字符串,通过 key 可以找到 valu...

weixin_42412396的博客 685

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

sensitive-word 敏感词之 DFA 双数组实现源码学习

我们首先统一定义 trie-tree 的接口,和 leetcode 风格保持一致。多一个插入列表,便于日常使用。/*** 前缀树接口/*** 插入列表* @param words 列表/*** 插入单词* @param word 单词/*** 是否存在单词* @param word 单词* @return 结果/*** 是否以 xxx 开头* @param prefix 开头* @return 结果/*** 前缀树接口/*** 插入列表。

1807

DoubleArrayTrie : DAT双数组Trie树

本文参考: http://www.cnblogs.com/ooon/p/4883159.html推荐: 码农场的《双数组Trie树(DoubleArrayTrie)Java实现》 外文《An Implementation of Double-Array Trie》DoubleArrayTrie  双数组Tire树是Tire树的升级版,Tire取自英文Retrieval中的一部分,即检索树,又称作

xlxxcc的专栏 1万+

Trie树及其优化

介绍Trie树及Double Array Trie优化

Rhine 2066

Java AC自动机实战:构建高效敏感词过滤系统

本文详细介绍了如何使用Java实现AC自动机(Aho-Corasick Automaton)构建高效的敏感词过滤系统。通过Trie树和失败指针机制,AC自动机能在单次扫描中检测所有预定义模式串,时间复杂度仅为O(n)。文章还提供了Java实现代码、性能优化技巧及Web应用集成实践,帮助开发者快速构建高性能的敏感词过滤解决方案。

sql99的博客 195

双数组Trie原理与工程实践:从搜索联想到敏感词过滤

在分词、词典匹配、敏感词过滤与搜索联想等场景中,数据结构的选择往往决定系统的性能上限。双数组Trie(Double Array Trie)作为一种高效的前缀匹配结构,通过base和check两个整型数组压缩传统字典树的指针开销,实现紧凑的内存布局与快速查询。它的核心原理是使用离散状态转移替代节点引用,从而在十万级词典规模下保持毫秒级响应,并支持前缀枚举与词频统计。当文本中需要匹配任意位置的关键词时,双数组还可与AC自动机结合,利用fail数组完成多模式匹配,广泛用于内容安全、智能提示和搜索引擎建议器。此外,

weixin_34101914的博客 225

java 模糊匹配_从一个模糊词查询需求的处理方案讨论到一种极速匹配方案的实现...

背景 两年多前的一天(2018年的某天),我们的产品经理突然找到我,说我们的广告业务上线后效果不错,但是需要做敏感词过滤处理,需要接入一个模糊词词典和一个精确词词典。然后我拿到了这两份词典,两份违禁词加起来总量近100w条。 这个需求简单来说就是如果用户的查询词中命中了违禁词的话是不能出广告的。如:用户query=怎么去故宫博物院,因为命中了故宫博物院,所以不能出广告。 最终我合并了这两份词表,并...

weixin_42346710的博客 1553

Aho Corasick自动机结合DoubleArrayTrie极速多模式匹配

2019独角兽企业重金招聘Python工程师标准>>> ...

weixin_34360651的博客 1891

ac算法 java_Java实现AC自动机全文检索

第一步,构建Trie树,定义Node类型:/*** Created by zhaoyy on 2017/2/7.*/interface Node {char value();boolean exists();boolean isRoot();Node parent();Node childOf(char c);Node fail();void setFail(Node node);void set...

weixin_33838871的博客 339

AC 算法的trie树实现(java版)

AC 算法的一种实现(java版) 前言: AC算法是最早

2009

Java Aho-Corasick 自动机在敏感词过滤中的高效应用

本文深入探讨了Aho-Corasick自动机在Java敏感词过滤中的高效应用。通过对比暴力匹配,详细解析了AC自动机结合Trie树与KMP算法的核心原理,并提供了完整的Java实现代码。文章进一步介绍了双数组Trie等性能优化方案,以及如何在高并发场景(如Netty框架)中集成,旨在帮助开发者构建高性能、可扩展的敏感词过滤系统。

v6b7n8m9q0的博客 783
上一篇: 双数组trie树详解
下一篇: 利用conda安装tensorflow2.0 和 pytorch(带镜像)
muyuu
博客等级 码龄9年 166粉丝 47原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值