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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/163484.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【JavaEE】计算机是如何工作的

计算机是如何工作的 冯诺依曼体系操作系统操作系统的概念与定位进程和任务操作系统对进程的管理PCB 的相关信息 冯诺依曼体系 现代的大多数计算机, 都遵循冯诺依曼体系 CPU 中央处理器: 进行算术运算和逻辑判断存储器: 分为外存和内存, 用于存储数据(使用二进制方式存储)输入…

竞赛 深度学习乳腺癌分类

文章目录 1 前言2 前言3 数据集3.1 良性样本3.2 病变样本 4 开发环境5 代码实现5.1 实现流程5.2 部分代码实现5.2.1 导入库5.2.2 图像加载5.2.3 标记5.2.4 分组5.2.5 构建模型训练 6 分析指标6.1 精度,召回率和F1度量6.2 混淆矩阵 7 结果和结论8 最后 1 前言 &…

竞赛选题 深度学习YOLO抽烟行为检测 - python opencv

文章目录 1 前言1 课题背景2 实现效果3 Yolov5算法3.1 简介3.2 相关技术 4 数据集处理及实验5 部分核心代码6 最后 1 前言 🔥 优质竞赛项目系列,今天要分享的是 🚩 基于深度学习YOLO抽烟行为检测 该项目较为新颖,适合作为竞赛课…

简单测试一下 展锐的 UDX710 性能

最近在接触 联通5G CPE VN007 ,发现使用的是 展锐的Unisoc UDX710 CPU,正好简单的测试一下这颗CPU CPU信息 UDX710 是一颗 双核 ARM Cortex-A55 处理器,主频高达 1.35GHz processor : 0 BogoMIPS : 52.00 Features : fp…

Django中ORM框架的各个操作

我们会好奇,python这么简洁的语言,数据查询是如何做的呢?我将进一步详细和深入地介绍Django中ORM框架的各个方面,包括MySQL的增删改查和复杂查询。让我们分步骤进行。 ORM框架介绍 Django的ORM框架是一个用于与数据库进行交互的工…

Spring Boot项目中使用 TrueLicense 生成和验证License(附源码)

1、Linux 在客户linux上新建layman目录,导入license.sh文件, [rootlocalhost layman]# mkdir -p /laymanlicense.sh文件内容: #!/bin/bash # 1.获取要监控的本地服务器IP地址 IPifconfig | grep inet | grep -vE inet6|127.0.0.1 | awk {p…

【LeetCode】 412. Fizz Buzz

题目链接 文章目录 Python3 【O(n) O(1)】C.emplace_back() 【C 11 之后】 Python3 【O(n) O(1)】 初始版本 class Solution:def fizzBuzz(self, n: int) -> List[str]:ans []for i in range(1, n1):if i % 5 0 and i % 3 0:ans.append("FizzBuzz")elif i % …

07 创建型模式-单例模式

1.单例模式介绍 单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一,此模式保证 某个类在运行期间,只有一个实例对外提供服务,而这个类被称为单例类。 2.使用单例模式要做的两件事 保证一个类只有一个实例…

【API篇】五、Flink分流合流API

文章目录 1、filter算子实现分流2、分流:使用侧输出流3、合流:union4、合流:connect5、connect案例 分流,很形象的一个词,就像一条大河,遇到岸边有分叉的,而形成了主流和测流。对于数据流也一样…

GitHub-使用 Git工具 创建密钥id_rsa.pub

快速导航 步骤1 打开Git Bash步骤2 输入指令【ssh-keygen】步骤3 打开创建的公钥文件步骤4 复制其中所有内容步骤5 打开GitHub中的Setting界面步骤6 添加SSH keys 步骤1 打开Git Bash 打开Git Bash 工具 步骤2 输入指令【ssh-keygen】 输入指令【ssh-keygen】,并…

【四:httpclient的使用】

目录 1、Demo案例2、请求一个带cookies的get请求3、请求一个带cookies的post请求案例一,案例二的properties的配置 1、Demo案例 public class MyHttpClient {Testpublic void test1() throws IOException {//用来存放我们的结果String result;HttpGet get new Htt…

安装VSCode,提升工作效率!iPad Pro生产力进阶之路

文章目录 前言1. 本地环境配置2. 内网穿透2.1 安装cpolar内网穿透(支持一键自动安装脚本)2.2 创建HTTP隧道 3. 测试远程访问4. 配置固定二级子域名4.1 保留二级子域名4.2 配置二级子域名 5. 测试使用固定二级子域名远程访问6. iPad通过软件远程vscode6.1 创建TCP隧道 7. ipad远…

模型量化笔记--KL散度量化

KL散度量化 前面介绍的非对称量化中,是将数据中的min值和max值直接映射到[-128, 127]。 同样的,前面介绍的对称量化是将数据的最大绝对值 ∣ m a x ∣ |max| ∣max∣直接映射到127。 上面两种直接映射的方法比较粗暴,而TensorRT中的int8量化…

openGauss学习笔记-102 openGauss 数据库管理-管理数据库安全-客户端接入之查看数据库连接数

文章目录 openGauss学习笔记-102 openGauss 数据库管理-管理数据库安全-客户端接入之查看数据库连接数102.1 背景信息102.2 操作步骤 openGauss学习笔记-102 openGauss 数据库管理-管理数据库安全-客户端接入之查看数据库连接数 102.1 背景信息 当用户连接数达到上限后&#…

小黑子—Maven基础

Maven基础 一 小黑子的Maven学习1. Mavn的介绍2. Maven基础概念2.1 仓库2.2 坐标2.3 仓库配置 3. 手动写一个maven项目3.1 Maven项目构建命令3.2 插件创建工程 4. IDEA下的maven项目5. 依赖管理5.1 依赖配置5.2 依赖传递5.3 可选依赖(不透明)5.4 排除依赖…

【一:实战开发testng的介绍】

目录 1、主要内容1.1、为啥要做接口测试1.2、接口自动化测试落地过程1.3、接口测试范围1.4、手工接口常用的工具1.5、自动化框架的设计 2、testng自动化测试框架基本测试1、基本注解2、忽略测试3、依赖测试4、超时测试5、异常测试6、通过xml文件参数测试7、通过data实现数据驱动…

UWB十个知识点

UWB是一直被基于厚望的高精度定位技术 1:定位技术及UWB特点 位置空间感知技术包括了GNSS、RFID、蓝牙和UWB,在室内和区域空间测量最具技术优势的技术是UWB。 GNSS是广域定位技术,室内以及建筑物旁边等场景,GNSS无法实现定位&am…

【微服务 SpringCloud】实用篇 · Ribbon负载均衡

微服务(4) 文章目录 微服务(4)1. 负载均衡原理2. 源码跟踪1)LoadBalancerIntercepor2)LoadBalancerClient3)负载均衡策略IRule4)总结 3. 负载均衡策略3.1 负载均衡策略3.2 自定义负载…

企业IT资产设备折旧残值如何计算

环境: 企业/公司 IT资产 问题描述: 企业IT设备折旧残值如何计算? 解决方案: 1.按三年折旧 净值原值-月折旧额折旧月份 , 月折旧额原值(1-3%)/36 折旧月份ROUND(E2*(1-3%)/36,2) 2.净值E2-F2*G2

vue使用pdf 导出当前页面,(jspdf, html2canvas )

需要安装两个插件 npm install html2canvas jspdfyarn add html2canvas jspdf<div class"app-container" id"pdfPage">我是内容 </div><el-button size"mini" click"onExportPdf">导出数据</el-button>onexp…