【数据结构】前缀树的模拟实现

目录

1、什么是前缀树?

2、模拟实现

2.1、前缀树节点结构

2.2、字符串的添加

2.3、字符串的查寻

2.3.1、查询树中有多少个以字符串"pre"作为前缀的字符串

2.3.2、查询某个字符串被添加过多少次 

2.4、字符串的删除

3、完整代码


 

1、什么是前缀树?

前缀树又名字典树,单词查找树,Trie树,是一种多路树形结构,是哈希树的变种,和hash效率有一拼,是一种用于快速检索的多叉树结构,。典型应用是用于统计和排序大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词颛统计。

它的优点:最大限度地减少无谓的字符比较,查询效率比哈希表还高。
Trie的核心思想是空间换时间。利用字符串的公共前缀来降低査询时间的开销以达到提高效率的目的。
Trie树的缺点:Trie树的内存消耗非常大。
性质:不同字符串的相同前缀只保存一份。
操作:查找,插入,删除。

注:本文章的前缀树功能都是基于字符串的

举个例子,例1:假设有:“abc”,“adf”,“cf”,“abcf”,“adf” 这五个字符串,则前缀树结构入下所示

2、模拟实现

以下的解析都是以上面的例1作为例子

2.1、前缀树节点结构

解释:创建一个前缀树节点类,前缀树节点中,我们并不放值,也就是并不放字符。我们把值,也就是字符,防止当前节点通往父节点的路上。前缀树节点类中有三个属性:int整形 pass、int整形 end  和  前缀树节点数组 nextS。

pass:pass用于记录当前这个节点经历过多少次,也就是上一级节点到当前节点的这条路通过了几次,这非常有助于查询树中有多少个字符串以某个字符串作为前缀。

end:记录这个节点是多少个字符串的结尾节点,这非常有助于查询某个字符串被加入过多少次。

节点数组 nextS[ ]:这是一个前缀树节点数组,用于记录当前这个节点通向下一个节点的路,比如:nextS[0] == null 没有走向'a’的路;nextS[0] != null 有走向'a’的路;......;nextS[25]!= nu1l 有走向'z’的路。这里数组中下标与字符的对应关系为:0->a; 1->b; 2->c; ......; 24->y; 25->z ,这钟关系在代码中的转换方式为:先定义一个下标变量index,然后每次要访问数组时,先让下标变量index等于要转换的字符减去字符‘a’就可以了,即:比如字符‘c’的转换为 index = 'c' - 'a'; 这条语句执行完后index会等于2,也就成功把字符'c'转换成数字2了。

节点结构代码

//前缀树的节点结构类public static class TreeNode {public int pass;  //pass用于记录当前这个点经历过多少次public int end;  //表示这个节点是多少个字符串的结尾节点//nextS是当前节点的下级节点,本实例中数组中的元素个数为26,从a到zpublic TreeNode[] nextS;  // HashMap<Char, TreeNode> nextS; 当字符种类特别多的时候,可以用HashMappublic TreeNode() {pass = 0;end = 0;// nextS[0] == null 没有走向'a’的路// nextS[0] != null 有走向'a’的路// ...// nextS[25]!= null 有走向'z’的路nextS = new TreeNode[26];}}

2.2、字符串的添加

字符串的添加从root节点开始,先把要添加的字符串转换成字符数组,然后从左往右开始遍历添加。

如当前节点node要添加一个字符,添加的规则为:

注:数组nextS[ ]中的数组元素都是前缀树节点类型,其实就是节点

当node的nextS[ ]中与该字符对应的数组元素为空时:那就要先为这个数组元素初始化,给这个数组元素new一个实例对象,这个实例对象起始就是一个新创建的节点,然后来到这个新建的节点,让这个节点的pass加一,然后看当前添加的这个字符在字符数组是不是最后一个字符:如果是,则还需要让这个节点的end加一;如果不是,则按这种规则继续添加字符数组中后面还没添加的字符。

当node的nextS[ ]中与该字符对应的数组元素不为空时:那就直接来到这个数组元素,也就是这个节点,让这个节点的pass加一,然后看当前添加的这个字符在字符数组是不是最后一个字符:如果是,则还需要让这个节点的end加一;如果不是,则按这种规则继续添加字符数组中后面还没添加的字符。

实现代码: 

//把字符串word加入到树中public void insert(String word) {if (word == null) {return;}char[] chs = word.toCharArray();  //把字符串word转换成字符数组TreeNode node = root;node.pass++;int index = 0;for (int i=0; i<chs.length; i++) {  //从左往右遍历字符index = chs[i] - 'a';  //由字符对应成要走哪条路if (node.nextS[index] == null) {//当node的nextS[ ]中与该字符对应的数组元素为空时node.nextS[index] = new TreeNode();}node = node.nextS[index];  //来到下一个节点node.pass++;}//当for循环结束了,就说明字符数组中的最后一个字符也添加完了,所以当前节点的end加一node.end++;}

2.3、字符串的查寻

2.3.1、查询树中有多少个以字符串"pre"作为前缀的字符串

例:比如我们要查询例1中的五个字符串中有多少个字符串以空字符串“ ”作为前缀,我们只需要返回root节点的pass,可以理解为root节点通往上一级节点的路为空,和空字符串对应,所以直接返回root节点的pass;

例:再比如我们要查询例1中的五个字符串中有多少个字符串以字符串“ab”作为前缀,我们先查看root下的a这条路是不是为空,如果为空就直接返回0,不为空就来到a这条路连接的下一个节点,然后我们查看来到的这个节点下的b这条路是不是为空,如果为空就直接返回0,不为空就来到b这条路连接的下一个节点,然后返回b这条路连接的下一个节点的pass,也就是2,这样我们就查询到了例1中的五个字符串中有2个字符串以字符串“ab”作为前缀。

以上两个例子都是在树中存在以字符串"pre"作为前缀的字符串的情况,如果不存在,那么在遍历字符数组时就会遇到有node的nextS[ ]中与当前查询字符对应的数组元素为空的情况,这时候直接返回0,因为这种情况就是表面树中不存在字符串“pre”,那就跟不可能存在以字符串"pre"作为前缀的字符串了

例:比如我们要查询例1中的五个字符串中有多少个字符串以字符串“aec”作为前缀,当我们来到a这条路连接的这个节点时,我们会发现这个节点的nextS数组中与字符‘e’对应的数组元素是空的,所以直接返回0。

实现代码: 

//查询所有加入的字符串中,有多少字符串是以字符串pre作为前缀的public int prefixNumber(String pre) {if (pre == null) {return 0;}char[] chs = pre.toCharArray();  //把字符串word转换成字符数组TreeNode node = root;int index = 0;for (int i=0; i<chs.length; i++) {index = chs[i] - 'a';if (node.nextS[index] == null) {return 0;}node = node.nextS[index];  //来到下一个节点}return node.pass;}

2.3.2、查询某个字符串被添加过多少次 

查询某个字符串被添加过多少次 和 查询树中有多少个以字符串"pre"作为前缀的字符串的规则是一样的,只是返回的节点属性不同,这里要返回的是节点的end,当字符数组遍历完后就会来到要查询的字符串的最后一个字符所对应的节点,然后返回这个节点的end就是这个字符串被添加过多少次 的结果了。

实现代码:

//查询word这个字符串加入过几次public int search(String word) {if (word == null) {return 0;}char[] chs = word.toCharArray();  //把字符串word转换成字符数组TreeNode node = root;int index = 0;for (int i=0; i<chs.length; i++) {index = chs[i] - 'a';if (node.nextS[index] == null) {return 0;}node = node.nextS[index];  //来到下一个节点}return node.end;}

2.4、字符串的删除

删除字符串和添加字符串规则相似,只是添加时是对节点的pass和end进行加一,而删除时是对节点的pass和end进行减一

实现代码: 

//删除字符串public void delete(String word) {if (search(word) != 0) {  //确定树中确实加入过word,才去执行删除操作char[] chs = word.toCharArray();TreeNode node = root;node.pass--;  //当前节点的pass减一int index = 0;for (int i=0; i<chs.length; i++) {index = chs[i] - 'a';if (--node.nextS[index].pass == 0) {//java会自动去释放内存空间,所以java里可以直接把node下级的路直接标空node.nextS[index] = null;return;}node = node.nextS[index];}node.end--;  //node已经来到了字符串word中的最后一个字符,然后这个node的end减一}}

3、完整代码

public class Code01_TrieTree {//前缀树的节点结构类public static class TreeNode {public int pass;  //pass用于记录当前这个点经历过多少次public int end;  //这个节点是多少个字符串的结尾节点//nextS是当前节点的下级节点,本实例中数组中的元素个数为26,从a到zpublic TreeNode[] nextS;  // HashMap<Char, TreeNode> nextS; 当字符种类特别多的时候,可以用HashMappublic TreeNode() {pass = 0;end = 0;// nextS[0] == null 没有走向'a’的路// nextS[0] != null 有走向'a’的路// ...// nextS[25]!= null 有走向'z’的路nextS = new TreeNode[26];}}//前缀树的类,内涵前缀树的构造方法和字符串的加入、删除、查找字符串加入了几次和以字符串pre作为前缀的字符串有多少个public static class Tree {private TreeNode root;public Tree() {root = new TreeNode();}//把字符串word加入到树中public void insert(String word) {if (word == null) {return;}char[] chs = word.toCharArray();  //把字符串word转换成字符数组TreeNode node = root;node.pass++;int index = 0;for (int i=0; i<chs.length; i++) {  //从左往右遍历字符index = chs[i] - 'a';  //由字符对应成要走哪条路if (node.nextS[index] == null) {//当node的nextS[ ]中与该字符对应的数组元素为空时node.nextS[index] = new TreeNode();}node = node.nextS[index];  //来到下一个节点node.pass++;}//当for循环结束了,就说明字符数组中的最后一个字符也添加完了,所以当前节点的end加一node.end++;}//删除字符串public void delete(String word) {if (search(word) != 0) {  //确定树中确实加入过word,才去执行删除操作char[] chs = word.toCharArray();TreeNode node = root;node.pass--;  //当前节点的pass减一int index = 0;for (int i=0; i<chs.length; i++) {index = chs[i] - 'a';if (--node.nextS[index].pass == 0) {//java会自动去释放内存空间,所以java里可以直接把node下级的路直接标空node.nextS[index] = null;return;}node = node.nextS[index];}node.end--;  //node已经来到了字符串word中的最后一个字符,然后这个node的end减一}}//查询word这个字符串加入过几次public int search(String word) {if (word == null) {return 0;}char[] chs = word.toCharArray();  //把字符串word转换成字符数组TreeNode node = root;int index = 0;for (int i=0; i<chs.length; i++) {index = chs[i] - 'a';if (node.nextS[index] == null) {return 0;}node = node.nextS[index];  //来到下一个节点}return node.end;}//查询所有加入的字符串中,有多少字符串是以字符串pre作为前缀的public int prefixNumber(String pre) {if (pre == null) {return 0;}char[] chs = pre.toCharArray();  //把字符串word转换成字符数组TreeNode node = root;int index = 0;for (int i=0; i<chs.length; i++) {index = chs[i] - 'a';if (node.nextS[index] == null) {return 0;}node = node.nextS[index];  //来到下一个节点}return node.pass;}}}

 

推荐:

java数据结构(哈希表—HashMap)含LeetCode例题讲解-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/m0_65277261/article/details/134712832?spm=1001.2014.3001.5501【计算机组成原理】存储器知识-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/m0_65277261/article/details/134770339?spm=1001.2014.3001.5501【计算机网络】(网络层)定长掩码和变长掩码_定长子网掩码和变长子网掩码-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/m0_65277261/article/details/134606175?spm=1001.2014.3001.5501

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

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

相关文章

K8S系列文章之 [使用 Alpine 搭建 k3s]

官方文档&#xff1a;K3s - 轻量级 Kubernetes | K3s 官方描述&#xff0c;可运行在 systemd 或者 openrc 环境上&#xff0c;那就往精简方向走&#xff0c;使用 alpine 做系统。与 RHEL、Debian 的区别&#xff0c;主要在防火墙侧&#xff1b;其他基础配置需求类似&#xff0…

[每周一更]-(第86期):PostgreSQL入门学习和对比MySQL

入门学习PostgreSQL可以遵循以下步骤&#xff1a; 安装 PostgreSQL&#xff1a; 首先&#xff0c;你需要在你的计算机上安装 PostgreSQL。你可以从 PostgreSQL 官方网站 下载适合你操作系统的安装包&#xff0c;并按照官方文档的指导进行安装。 学习 SQL&#xff1a; PostgreS…

【大厂AI课学习笔记】【1.5 AI技术领域】(7)图像分割

今天学习到了图像分割。 这是我学习笔记的脑图。 图像分割&#xff0c;Image Segmentation&#xff0c;就是将数字图像分割为若干个图像子区域&#xff08;像素的集合&#xff0c;也被称为超像素&#xff09;&#xff0c;改变图像的表达方式&#xff0c;以更容易理解和分析。 …

〔Part1〕YOLOv5:原理+源码分析(配置文件、网络模块、损失函数、跨网格匹配策略)

1. 前置知识 1.1 YOLO 算法的基本思想 首先通过特征提取网络对输入图像提取特征&#xff0c;得到一定大小的特征图&#xff0c;比如 13x13&#xff08;相当于416x416 图片大小&#xff09;&#xff0c;然后将输入图像分成 13x13 个 grid cells&#xff1a; YOLOv3/v4&#xf…

leetcode:17.电话号码的字母组合

题意和解题思路&#xff1a; 数字和字母的一一对应采用二维数组或者map映射。 这里我采用数组进行存储进而实现一一对应。由于我们无法知道for循环嵌套几层&#xff0c;因为这个是由于输入来确定的&#xff0c;所以我们可以用回溯算法中的递归来进行实现。 树形结构&#xff…

git版本回退。git reset参数详解,特殊提交情形下的git push操作(CR等常见场景),git reflog和git log的详解。

切换分支可以使用 git checkout <> 或者git switch ... 创建分支可以使用 git checkout -b <. ...> 或 git branch <...> git checkout <...> git reset --hrad HEAD^ -- 今日份chatgpt git reset --hard HEAD^ 的含义如下&#xff1a; git reset …

利用知识图谱构建医疗问答

1、准备数据集 数据集下载地址&#xff1a;https://github.com/wangle1218/QASystemOnMedicalKG/blob/master/data/medical.json 2、导入相关包 from py2neo import Graph,Node,Relationship # 在cmd中&#xff0c;输入neo4j.bat console并回车 import pandas as pd3、连接N…

【web前端开发】HTML及CSS简单页面布局练习

案例一 网页课程 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" content"widthdevice-wi…

Android 识别车牌信息

打开我们心爱的Android Studio 导入需要的资源 gradle //开源车牌识别安卓SDK库implementation("com.github.HyperInspire:hyperlpr3-android-sdk:1.0.3")button.setOnClickListener(v -> {Log.d("Test", "");try (InputStream file getAs…

「递归算法」:子集(两种解法)

一、题目 给你一个整数数组 nums &#xff0c;数组中的元素 互不相同 。返回该数组所有可能的子集&#xff08;幂集&#xff09;。 解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。 示例 1&#xff1a; 输入&#xff1a;nums [1,2,3] 输出&#xff1a;[[],[1],[2]…

-打印流-

打印流分为字节打印流&#xff1a;PrintStream 字符打印流&#xff1a;PrintWriter特点1&#xff1a;都是只能输出 不能读取 字节打印流&#xff1a; 构造方法&#xff1a;主要用上面的两个构造 成员方法&#xff1a; //创建字节打印流对象&#xff1a;ctrlp注意参数 Prin…

【DDD】学习笔记-理解领域模型

Eric Evans 的领域驱动设计是对软件设计领域的一次重新审视&#xff0c;是在面向对象语言大行其道时对数据建模的“拨乱反正”。Eric 强调了模型的重要性&#xff0c;例如他在书中总结了模型在领域驱动设计中的作用包括&#xff1a; 模型和设计的核心互相影响模型是团队所有成…

【Spring源码解读!底层原理高级进阶】【上】探寻Spring内部:BeanFactory和ApplicationContext实现原理揭秘✨

&#x1f389;&#x1f389;欢迎光临&#x1f389;&#x1f389; &#x1f3c5;我是苏泽&#xff0c;一位对技术充满热情的探索者和分享者。&#x1f680;&#x1f680; &#x1f31f;特别推荐给大家我的最新专栏《Spring 狂野之旅&#xff1a;底层原理高级进阶》 &#x1f680…

L1-096 谁管谁叫爹

一、题目 二、解题思路 依据题意判断即可。 三、代码 #include<iostream> using namespace std; #include<cmath> int main() {int n;cin>>n;while(n--){int n1,n2,s10,s20;cin>>n1>>n2;for(int i1;n1/i>0;i*10){s1(n1/i%10);}for(int i1;n…

k8s学习(RKE+k8s+rancher2.x)成长系列之简配版环境搭建(二)

三、简配版集群&#xff0c;适用于demo环境 1.集群架构设计 主机名角色配置(核数&#xff0c;内存&#xff0c;磁盘)MasterRKE,controlplane,etcd,worker,rancher-master2C 8G 40GSlaver1controlplane,worker,rancher-master2C 8G 40GSlaver2controlplane,worker,rancher-mas…

【第三十五节】idea项目的创建以及setting和Project Structure的设置

项目创建 Project Structure的设置 点击file ~ Project Structure 进入 进入view/Appearance 选中Toolbar 就会出现状态栏

# Memory Analyzer (MAT) 在实际开发中的使用

Memory Analyzer (MAT) 在实际开发中的使用 文章目录 Memory Analyzer (MAT) 在实际开发中的使用概述注意点基本使用检查概述获取直方图View the Dominator Tree到GC根的路径 使用示例制作堆dumpHeapDumpOnOutOfMemoryErrorJmap 生成堆Dump Mat打开堆快照HistogramThread Overv…

使用deepspeed继续训练LLAMA

目录 1. 数据训练配置 2. 模型载入 3. 优化器设置 4. DeepSpeed 设置 5. DeepSpeed 初始化 6. 模型训练 LLAMA 模型子结构&#xff1a; 1. 数据训练配置 利用 PyTorch 和 Transformers 库创建数据加载器&#xff0c;它支持单机或多机分布式训练环境下的数据加载与采样。涉…

Uniapp(uni-app)学习与快速上手教程

Uniapp&#xff08;uni-app&#xff09;学习与快速上手教程 1. 简介 Uniapp是一个跨平台的前端框架&#xff0c;允许您使用Vue.js语法开发小程序、H5、安卓和iOS应用。下面是快速上手的步骤。 2. 创建项目 2.1 可视化界面创建 1、打开 HBuilderX&#xff0c;这是一款专为uni…

mysql 中文编码问题

前言 最近在学springboot整合mybatisplus技术&#xff0c;用到mysql数据库&#xff0c;然后发现在windows下插入数据表会出现中文乱码现象 (例如 “我是谁” 在数据库中就成了 “???”) windows show variables like %char%;建表时, 设置默认charset为gbk create table u…