C++实现AC自动机,剪枝、双数组压缩字典树!详解双数组前缀树(Double-Array Trie)剪枝字典树(Patricia Trie)
代码在:github.com/becomequantum
最近研究了一下字典树,什么AC自动机,双数组压缩字典树,剪枝字典树都自己写代码实现了一下。这本该是本科学数据结构时该玩明白的东西,我到现在才会玩。本视频主要介绍一下双数组和剪枝这两种压缩字典树的方式,尤其是双数组。我发现中文科普双数组字典树的文章都没把问题讲清楚,我看了好几篇文章都没看明白,后来还是看了这篇英文文章才搞明白。不得不说,科普文章还是老外写的更加通俗易懂。其实双数组压缩这个方法的确很简单。

先来说剪枝字典树,因为它的概念一看图就明白了,“剪枝”是我给起的名字,英文名叫“Patricia Trie”。请看上图,左边是基础的字典树,可以看出,在这个包含六个单词的树中,一共有节点28个,但有分支的节点,也就是子节点不止一个的节点,就只有上图中标为红色的四个。我在Github上找了个有12万单词的英文词库做了下统计,形成的字典树中分支节点占比只有百分之十七。要知道,字典树中的这些节点和它们之间的转换关系,都是存在一个二维数组中的。上面这个小字典树的状态转换表就如下图所示:

这个表就是我的代码打印出来的。这是一个26乘28的表,26就是英文字母的个数。如果是那个12万词库,这个表就是26乘26万。听起来好像也不是很大啊!是的,英文这样弄还行,那要是中文呢?万国码里的汉字有两万多个啊!我试了一下,中文字典树如果想建一个两万多乘多少的表,结果就直接实现不了,数组大小超标了。仔细看上面这个表,大家猜这个表的利用率有多少呢?也就是表中存的节点号的个数除以总容量。我本想着写点代码统计一下,结果思忖了一下发现这个利用率就不需要统计,因为它就恒等于二十六分之一。大家可以仔细看上表琢磨一下这是为啥。

所以说用二维数组存字典树效率是很低的,要是中文那就更低了,低到都实现不了。一个改进办法就是把数组换成哈希表,这个很好实现啊,C++里用模板换一下就行了,一套代码就可以搞定这两种类型的节点表。改为哈希表之后,中文字典树就能实现了,但问题是,改为哈希表之后查词会慢些,毕竟读哈希表没有读数组块。我写代码实测也的确是这样。所以还得想办法,办法之一就是把字典树中的线性分支给压缩合并掉,如上图右边所示。
这样字典树里就会存在第二种节点,在上图右边用方框表示,节点编号我用负数表示,以示区别。第二种节点我管它叫尾枝节点。这种节点里不需要存转换表,但需要存它包含的词尾上的那几个字母。当从分支节点查到尾枝节点时,就不需要再往下查了,直接改为字串比较,把待查词剩下的字母和节点里存的比一下就行了。这个改进方法看起来还不错,既减少了节点数量,又把查几次表改为了字串比较,理论上是会更快一些的。
我没有写代码实现如何一点点的构建这样的字典树,而是写了个整体剪枝代码,也就是把一个已经构建好的基础字典树一下子改造成剪枝字典树。这个改造算法在深度优先遍历的基础上加些内容就能实现。改造完测了一下速度发现,查词速度还没基础字典树快,似乎改了个寂寞。这大概是因为我的实现方法还不够优化,多了个节点又会多一个数组存储这种节点。查字典树是避免不了随机读取数据的,多一种节点就意味着,字典树要查的数据更加分散,缓存没命中的概率就会增加,进而导致整体耗时是增加了,不是减小了。所以要想剪枝字典树效率更高,还得在数据存储的紧凑性上继续想办法。

接着说双数组压缩法,这个方法的意思就是,把上面这么多行稀疏数组中的内容都塞进两个数组里。那这该咋塞呢?我们从上表中截取三行,若干列内容来说明。请看上面这个小点的表,我们先在下面新建一个存储“下个状态”的数组,它就只有一行。然后把上面表中后两行的数据直接往下放进去。这样放是能放,但放一块之后不就不知道某个数据原来是来自哪一行了吗?于是就还需要另一个,在英文文献中叫做check的数组,这个数组会在“下个状态”号数据对应的位置存它原来是属于哪一行。

如上图所示,“下个状态”行中,19,23的下面都存着18,示意着它们都来自“当前状态号”是18的那一行。这样在另外一行给“下个状态”行中的数据打上标签,就知道它是来自哪一行了。接下来的问题就是,第一行的13和第二行的17位置相同咋办?好办,错个位置,找个空塞进去就行。那这又会带来一个新问题,原来13是在字母e下面,现在跑到f下面去了,那又该怎么通过输入字符值检索到它呢?这就还需要一个base数组,里面存的是,每个当前状态号,在查“下个状态”表时需要的起始位置。


比如要查当前状态12,在输入为e时的下个状态号,我们需要先用base[12],把它的查表起始位置找出来,在上面这个例子中它是1。然后用这个1加上e的值,就得到了去查“下个状态”数组的索引位置。这时还不能确定这个位置的数据就是属于状态12的,还得用check[base[12] + e],去看看check数组里该位置的标签是不是12,如果是12,那就跳转到这下个状态,如果不是,那就是没查到下个状态。
双数组的原理就是这样了,这不明明有三个数组吗?双数组实际上指的是把上述的base和next_state合并成了一个数组。同样的,我也没写一个个插入单词,构建双数组字典树的代码,只写了个把构建好的字典树的状态表压缩成双数组的代码。这个代码要做的事情就是见缝插针,通过顺序递增base值,看看增加到多少的时候能正好能把当前行的数据都塞进next_state和check数组中空的位置中。这两个数组的长度大概是比所有节点数大一点点。大家猜猜这两个数组的利用率是多少?几乎接近百分之一百,因为很多行都只有一两个数据,很好塞。

经过实测发现,双数组压缩之后,查询效率有成倍的提升。双数组查询的时候,看起来所需要的运算多了一点点,没有直接查一个数组那么简单。但双数组能让被查数据变得更加紧凑,这样应该能提升CPU缓存命中率,所以耗时反而减少了。
另外我也写了在字典树的基础上构建AC自动机的代码,这回是广度优先遍历,顺便推荐一下上面这个没人看的视频,这位老师讲Fail指针的构建讲了好几遍,讲的比较清楚。我在这就懒得讲了,有兴趣的朋友可以去看代码,俺写的代码里注释比较多,比较好懂。

最后再来吐槽一下C++,如上图所示,上面这段代码,我就return后面少写了个分号,结果就报了个莫名其妙的“内部编译器错误”。当然不是所有没打分号的情况都会报这个错,而是在某些特定情况下会报。这都2023年了,C++编译器的报错能力还是不行,远不如Rust。不过从灵活性上来说,还是C++最好。比如模板编程,C++里的模板本质就是宏替换,没做过多的限制。Rust里的模板和C sharp比较像,限制较多,而这反而让有些代码不好写。C++里的模板还是挺好用的,两个类型只要部分形式相同,就都能往模板里套,驴头和马嘴都可以找个共同点套一个模板里去。
大脑视觉皮层运作机理简介,CNN其实不像它_哔哩哔哩_bilibili
相关文章:
C++实现AC自动机,剪枝、双数组压缩字典树!详解双数组前缀树(Double-Array Trie)剪枝字典树(Patricia Trie)
代码在:github.com/becomequantum 最近研究了一下字典树,什么AC自动机,双数组压缩字典树,剪枝字典树都自己写代码实现了一下。这本该是本科学数据结构时该玩明白的东西,我到现在才会玩。本视频主要介绍一下双数组和剪…...
防火墙规则顺序解决方案
防火墙是保护网络免受攻击的第一道防线,防火墙对互联网和公司IT网络之间的流量拥有绝对控制权,防火墙规则的配置处理调节流量的关键任务。 这些规则会仔细检查传入和传出流量,并根据规则中提到的条件允许或阻止它,防火墙规则越严…...
ZC-CLS381RGB颜色识别+8x8点阵指示(完)
文章目录 前言一、信号关联说明二、演示视频 前言 在前面两篇博客中,分别阐述了如何配置WS2812 RGB 8x8点阵,和如何配置颜色识别模块,本文将说明如何级联两个模块,以及演示两个模块级联后的运行效果。 一、信号关联说明 已知WS28…...
Stanford CS224N - word2vec
最近在听Stanford放出来的Stanford CS224N NLP with Deep Learning这门课,弥补一下之前nlp这块基础知识的一些不清楚的地方,顺便巩固一下基础知识😁 关于word2vec: 1.为什么要把单词表示成向量 一开始人们造了一个类似于词典表…...
华为云云耀云服务器L实例评测|windows系统3389防爆破之安全加固教程
为什么要选择华为云云耀云服务器L实例: 华为云在全国范围内建立了多个数据中心,这些数据中心之间相互冗余,以确保高可靠性和可用性,用户可以选择最适合的区域来部署应用程序,以实现更好的性能和延迟。 相对于传统的物…...
零基础如何自学C#?
前言 本文来源于知乎的一个提问,提问的是一个大一软件工程专业的学生,他想要自学C#但是不知道该怎么去学,这让他感到很迷茫,希望有人能给他一些建议和提供一些学习方向。 个人建议 确认目标:自学C#首先你需要大概了解…...
Spring5学习笔记之整合MyBatis
✅作者简介:大家好,我是Leo,热爱Java后端开发者,一个想要与大家共同进步的男人😉😉 🍎个人主页:Leo的博客 💞当前专栏: Spring专栏 ✨特色专栏: M…...
GO 语言的方法??
GO 中的方法是什么? 前面我们有分享到 GO 语言的函数,他是一等公民,那么 GO 语言中的方法和函数有什么区别呢? GO 语言中的方法实际上和函数是类似的,只不过在函数的基础上多了一个参数,这个参数在 GO 语…...
【JavaEE】 多线程-初阶
多线程-初阶 1. 认识线程 1.1 概念 1) 线程是什么 多个线程组成了一个进程,线程好比是一跟光纤中的一个玻璃丝,进程是整根光纤。 一个进程中的线程共享这个进程中的资源(内存、硬盘) 2) 为什么需要线程 单核CPU发展出现瓶颈…...
小程序OCR身份证识别
使用两种OCR识别:小程序和腾讯云 1.基于微信小程序OCR插件实现身份证拍照、上传并OCR识别的示例: 首先,在小程序中添加身份证拍照的功能,可以使用wx.chooseImage()选择照片并使用wx.uploadFile()上传,代码如下&#…...
【算法学习】归并算法Merge Sort总结
归并排序思路简单,速度仅次于快速排序,为稳定排序算法,一般用于对总体无序,但是各子项相对有序的数列。 1. 基本思想 归并排序使用分治思想,分治模式下每一层递归有三个步骤: 分解(divide)&a…...
Swager如何使用
Swager是一个API文档自动生成工具,可以用于生成API接口文档,供开发者和用户查看和使用。它可以通过描述API接口的规范,自动生成API文档,使得API接口的发布和使用变得更加简单和规范。 下面是使用Swagger的步骤: 首先…...
DHorse v1.4.2 发布,基于 k8s 的发布平台
版本说明 优化特性 在集群列表增加集群版本;修改Jvm的GC指标名; 解决问题 解决shell脚本换行符的问题;解决部署历史列表页,环境名展示错误的问题;解决指标收集功能的异常; 升级指南 升级指南 DHorse…...
Java使用JJWT令牌
最近在B站大学学习Java开发,刚好学到登入验证,在使用JJWT令牌时踩了一些坑,在这里把代码和依赖给出,希望后来者得以借鉴。 依赖 <dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-api&l…...
“第四十四天”
这道题也不是难,但可能会忽略一种情况,当最大小出现在首位的时候,那个时候如果进行交换的话,大小值可能出现覆盖的情况,最终导致丢失最大值或者最小值,比如最大值 10 在第一位,最小值 0 随意&am…...
Unity Mono和.Net平台浮点算法的区别
static void TestFloat(){{//float speed2.0f/20;float speed 0.1f;float distance 2.0f;long needTime (long)(distance / speed);Log.Debug($"needTime{needTime}"); #if UNITY_EDITORif (needTime ! 19) #elseif (needTime ! 20)//.Net服务器和安卓手机 #endif…...
【SA8295P 源码分析 (二)】64 - QNX 与 Android GVM 显示 Dump 图片方法汇总
【SA8295P 源码分析】64 - QNX 与 Android GVM 显示 Dump 图片方法汇总 一、QNX侧1.1 surfacedump 功能1.2 screenshot 功能二、Android GVM 侧2.1 screencap -p 导出 PNG 图片2.2 screencap 不加 -p 参数,导出 RGB32 图片2.3 dumpsys SurfaceFlinger --display-id 方法系列文…...
shell命令以及运行原理和lLinux权限
shell命令以及运行原理 什么是shell shell是操作系统的外壳程序统称,我们是通过shell去和操作系统沟通的。 从技术角度,shell最简单的定义就是命令行解释器,主要包含两个功能: 将使用者的命令翻译给核心处理 将核心的处理结果…...
斯坦福JSKarel编程机器人使用介绍
斯坦福JSKarel编程机器人使用介绍 为了避免被编程语言固有的复杂性所困扰,有一个被称为卡雷尔(Karel)机器人的微型世界(microworld)的简化环境,可以让编程初学者从中学习理解编程的基本概念,而…...
SpringBoot中pom.xml不引入依赖, 怎么使用parent父项目的依赖
在Spring Boot项目中,如果你想使用父项目的依赖,而不想在pom.xml中显式引入依赖,你可以使用Maven的继承机制。 首先,确保你的Spring Boot项目是一个子项目,即它继承自一个父项目。要实现这一点,在pom.xml文…...
UE5 学习系列(二)用户操作界面及介绍
这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…...
前端倒计时误差!
提示:记录工作中遇到的需求及解决办法 文章目录 前言一、误差从何而来?二、五大解决方案1. 动态校准法(基础版)2. Web Worker 计时3. 服务器时间同步4. Performance API 高精度计时5. 页面可见性API优化三、生产环境最佳实践四、终极解决方案架构前言 前几天听说公司某个项…...
【JVM】- 内存结构
引言 JVM:Java Virtual Machine 定义:Java虚拟机,Java二进制字节码的运行环境好处: 一次编写,到处运行自动内存管理,垃圾回收的功能数组下标越界检查(会抛异常,不会覆盖到其他代码…...
【快手拥抱开源】通过快手团队开源的 KwaiCoder-AutoThink-preview 解锁大语言模型的潜力
引言: 在人工智能快速发展的浪潮中,快手Kwaipilot团队推出的 KwaiCoder-AutoThink-preview 具有里程碑意义——这是首个公开的AutoThink大语言模型(LLM)。该模型代表着该领域的重大突破,通过独特方式融合思考与非思考…...
cf2117E
原题链接:https://codeforces.com/contest/2117/problem/E 题目背景: 给定两个数组a,b,可以执行多次以下操作:选择 i (1 < i < n - 1),并设置 或,也可以在执行上述操作前执行一次删除任意 和 。求…...
sipsak:SIP瑞士军刀!全参数详细教程!Kali Linux教程!
简介 sipsak 是一个面向会话初始协议 (SIP) 应用程序开发人员和管理员的小型命令行工具。它可以用于对 SIP 应用程序和设备进行一些简单的测试。 sipsak 是一款 SIP 压力和诊断实用程序。它通过 sip-uri 向服务器发送 SIP 请求,并检查收到的响应。它以以下模式之一…...
保姆级教程:在无网络无显卡的Windows电脑的vscode本地部署deepseek
文章目录 1 前言2 部署流程2.1 准备工作2.2 Ollama2.2.1 使用有网络的电脑下载Ollama2.2.2 安装Ollama(有网络的电脑)2.2.3 安装Ollama(无网络的电脑)2.2.4 安装验证2.2.5 修改大模型安装位置2.2.6 下载Deepseek模型 2.3 将deepse…...
基于Java+MySQL实现(GUI)客户管理系统
客户资料管理系统的设计与实现 第一章 需求分析 1.1 需求总体介绍 本项目为了方便维护客户信息为了方便维护客户信息,对客户进行统一管理,可以把所有客户信息录入系统,进行维护和统计功能。可通过文件的方式保存相关录入数据,对…...
免费PDF转图片工具
免费PDF转图片工具 一款简单易用的PDF转图片工具,可以将PDF文件快速转换为高质量PNG图片。无需安装复杂的软件,也不需要在线上传文件,保护您的隐私。 工具截图 主要特点 🚀 快速转换:本地转换,无需等待上…...
uniapp 集成腾讯云 IM 富媒体消息(地理位置/文件)
UniApp 集成腾讯云 IM 富媒体消息全攻略(地理位置/文件) 一、功能实现原理 腾讯云 IM 通过 消息扩展机制 支持富媒体类型,核心实现方式: 标准消息类型:直接使用 SDK 内置类型(文件、图片等)自…...
