实现自定义的数据结构 —— 自然树

PTA 数据结构 哈夫曼树与哈夫曼编码 文章目录题目描述输入格式输出格式样例一点说明想法实现 题目描述 题目背景:介绍什么是哈夫曼树和哈夫曼编码, 不影响做题 哈夫曼树(Huffman Tree)又称最优二叉树,是一种带权路径长度最短的二叉树。所谓树的带权路径长度,就是树中所有的叶结点的权值乘上其到根结点的路径长度(若根结点为0层,叶结点到根结点的路径长度为叶结点的层数)。树的路径长度是从树根到每一结点的路径长度之和,记为WPL=(W1*L1+W2*L2+W3*L3+...+Wn*Ln),N个权值Wi(i=1,2,...n)构成一棵有N个叶结点的 阅读详情

        最近看了一些公司的面试题,发现涉及 IT技术,包括 AI 行业的题目都会涉及到最基本的 C/C++、数据结构和算法之类的,突发奇想写了一个貌似没啥用的数据结构,就当是复习了 [手工哭笑表情]

1 结构

图1.1 自然树的整体结构

        该自定义结构不同于二叉树及其他数据结构,每个节点的子节点个数不受限制,最大限度保留了数据的原始结构,并实现了其前序和后序遍历的方法。优点是节省了内存,但缺点则是基于链表结构查询的时间开销会相对较大。

举个例子,图1.1所示,即是该自定义树的一种结构

图1.1左边是一种直观的结构,Root为树的根节点,其子节点包括 A、B、C,节点 A 的子节点为 D,节点 B 的子节点为 E、F

【注】:Root 用来表示自然树的头节点,其 data 不储存任何有意义的数据,T是指向Root节点的指针,也就是头指针。

        而图1.1右边则是程序中的结构,为此设计了两种子结构 TNode 和 Node,TNode 用于表示树的节点信息,而 Node 则用于构成链表,组成某节点的子节点集合

1.1 节点结构 TNode

        如图1.2所示包括4种数据类型:(1)节点的名称 name、(2)数据 data、(3)双亲节点指针 parent、(4)子节点指针 child。data用于储存该节点的数据,parent 节点指针用于向上层查找,child 节点用于向下层查找。(图中白色块表示普通变量,橙色块表示指向TNode类型的指针变量,蓝色块表示指向Node类型的指针变量)

图1.2 TNode结构

程序定义如下:

struct TNode  	//结点结构
{
	std::string  name;		//结点名称
	TElemType    data;		//结点数据
	TNode 	  	*parent;	//双亲节点指针
	Node 		*child;		//子节点指针
};
typedef TNode * Tree;

【注】:name为节点的标识符(等同于 ID 标记),根据名称长度分配不同大小的动态内存,不可重复,TElemType为节点包含的数据类型,作者定义为 int,根据实际需要修改。Tree 用来表示整个树(树的头节点数据类型为TNode,故用指向头节点的指针来表示树)

1.2 链表的节点结构 Node

         如图1.3所示包括4种数据类型:(1)指向TNode节点的指针 tNode、(2)前一个链表节点的指针 pre、(3)下一个链表节点的指针 next。

图1.3 Node结构

         程序定义如下:

struct Node    // 链表结构
{
	TNode *tNode;    //指向TNode节点
	Node  *pre;      //指向前一个Node节点
	Node  *next;     //指向后一个Node节点
};
typedef  Node * LinkList;

        此处有个特殊的节点:链表的头节点(head)

        如图1.4所示,该头节点中 tNode 指向的 TNode 节点储存其双亲点的子节点的数量信息(由 data存储,图中举例该双亲节点的子节点共有3个,也就是说该head节点后还有3个后继节点)。而其 name 会自动命名为 “双亲名称” + “_head”,标记其为头节点(图中举例双亲名称为 “A”,故其头节点名称为“A_head”)。

图1.4 链表的头节点

【注】:因为链表的头节点没有前继节点,故 pre 始终为 NULL,而且也禁止为头节点添加子节点,所有 child 也始终为 NULL。

2 遍历

        对于一种自定义的数据结构来说,最核心需要实现的功能就是遍历,程序中对于不同功能设计了两种遍历算法:前序遍历 和 后序遍历。对于节点的插入操作,可以通过前序遍历或后序遍历来实现,而节点的删除操作因为涉及由后往前的动态内存的释放,则需使用后序遍历来实现。

2.1 前序遍历

        由于head节点的存在,故可进一步设计出两种遍历方式:(1)遍历所有节点,包括head(2)跳过所有head。

        由于兄弟节点插入设计为后插入(具体见第3章),故需要遍历head节点,即需要使用第(1)种遍历方式,而插入子节点(具体见第3章)或是单纯的输出显示所有节点信息,则第(2)种就可以。

        如图2.1所示为树的第(1)种:前序遍历所有节点,包括所有head

图2.1 前序遍历,包括所有节点

    具体到结构则如图2.2所示

图2.2 前序遍历所有

   

    核心逻辑如下,先递归遍历,再移动指针

 
for(int i=0; i< len+1; i++)
{
	_ShowAll_Pre(P->tNode);
	P = P->next;
}

    控制台输出如图2.3所示,格式为 (双亲节点name)  <——  (该节点的name: 该节点的data)。

图 2.3

【注】:Root 节点无双亲,故仅显示name和data。

        如图2.4所示为树的第(2)种:前序遍历,跳过所有head

图2.4 前序遍历,跳过所有head

    具体到结构如图2.4所示

图2.4 前序遍历,跳过Head

    核心逻辑如下,先移动指针,再递归遍历,同时由于先移动指针跳过了head节点,故需要循环的长度为 len

for(int i=0; i< len; i++)
{
	P = P->next;
    _ShowAll_Pre(P->tNode);
}

    控制台输出如图2.5所示

图2.5

2.2 后序遍历

        对于节点删除操作,因为要从该节点的最后一个子节点开始倒序释放(避免指针失效),故需使用后序遍历。同理,分为上述两种遍历方式:(1)遍历输出所有节点(2)跳过所有head

        如图2.6所示为树的第(1)种:后序遍历输出所有节点

图2.6 后序输出所有节点

        如图2.7所示为树的第(2)种:后序遍历输出所有节点,但跳过所有head

图2.7 后序遍历,跳过所有head

        控制台输出如图2.7所示:

图2.6

3 插入

        笔者实现了两种插入:(1)为该节点插入子节点(禁止为head节点插入子节点),该子节点会插入到最后一个位置;(2)在该节点的后面插入兄弟节点(可以 head 后插入)。

3.1 插入子节点

        如图3.1所示,为插入子节点的示意图,首先遍历所有节点(可跳过head),找到需要插入子节的节点(作为双亲节点)后,逐步检索其子节点至最后一个,然后生成新的节点,并令其 parent 指针指向其双亲节点,最后令新生成的节点与最后一个子节点相连接(原最后一个节点的 next 指针指向新生成的节点,新生成节点的 pre 指针指向原最后一个节点),同时此条链表的长度+1(head 节点的 data+1)。

图3.1 插入子节点

【注】:若​​​​该节点没有子节点,则先生成一个 head 节点再进行插入

3.2 在某一节点后插入兄弟节点

        如图3.2所示,遍历检索到该节点,然后生成新的节点,并令新生成节点的 parent 节点指向该节点,然后断开该节点与后续节点之间的指针连接,将新生成节点的指针连接至两节点之间,同时此条链表的长度+1(head 节点的 data+1)。

图3.2 插入兄弟节点

【注】:如果该节点没有后继节点,则只需将新生成节点的后继指针 next 指向 NULL 即可。

4 删除

        删除某一节点如图4.1所示,首先找到该节点和其后后继节点,然后断开后继节点与该节点和后后继节点之间的指针连接,并释放后继节点的内存,最后将该节点与后后继节点相连,同时 head 节点的 data-1。

图4.1 删除某一节点

        如果该节点有子节点呢,则需要通过后序遍历找到该节点的子节点的最后一个节点(子节点可能仍存在子节点,则需要层层遍历),倒序依次释放所有节点。

        如图4.2所示,假如需要删除图1.1中的 B 节点,由于 B 节点存在子节点,则需要将其及其所有子节点全部删除,并释放内存,删除顺序同后序遍历顺序,不过在删除链表最后一个子节点后需要回过来删除该链表的 head 节点。

图4.2 删除节点 B

        删除节点B后的控制台输出为(前序遍历所有):

图4.3 删除节点 B

        删除树则是删除所有节点(包括Root节点),并释放所有内存。

 

结语

未来有时间会加入与二叉树的相互转换功能,目前完成了第一版,可能仍有Bug,如果发现的话,还望指正 (∩_∩)

编写了两版:C语言和C++版本

OS:windows10;开发环境:Dev C++;编译器:MinGW GCC 4.8.1 32-bit Release(不过看控制台输出貌似使用的 G++ 编译器),所以如果编译失败的话建议换G++编译器试试

代码链接(C):https://download.youkuaiyun.com/download/jack__linux/12494399

代码链接(C++):https://download.youkuaiyun.com/download/jack__linux/14992267

 

 

 

 

你真的懂树吗?二叉树、AVL平衡二叉树、伸展树、B-树和B+树原理和实现代码详解... 树(Tree)是一种相当灵活的数据结构(上一节已经详细讲解了基本的数据结构:线性表、栈和队列),你可能接触过二叉树,但是树的使用并不限于此,从简单的使用二叉树进行数据排序,到使用B-树或B+树设计数据库引擎,以及目前热门的人工智能机器学习都使用到树,例如决策树(Decision Tree)和随机森林(Random Forest),而AVL平衡树和伸展树是二叉树的优化版。树的实现原理算法比较复杂,即... 阅读详情

相关推荐

Python量化交易实战:如何用vnpy+Tushare获取股票历史数据(附完整代码)

本文详细介绍了如何利用vn.py框架与Tushare数据接口,构建一个健壮的Python量化交易数据获取引擎。通过封装Tushare API,实现股票历史数据的自动化获取与格式转换,并适配vn.py的BarData结构,为策略回测与实盘交易提供稳定、高效的数据基础。文中提供了完整的代码实现,涵盖环境配置、核心封装逻辑及高级功能扩展。

cola5的博客 1224

C语言数据结构(11)--数组描述子节点的树

啥是树 之前所讲的线性表、队列、栈,实际上都是一种一对一的结构,而树是一种一对多的结构。 树这个名字起的非常形象,所以一个树的结构可如下图所示: 也就是说每个节点下面有N个节点(N>=0),且根节点数量小于1的数据结构为树。当根节点数量为0是,是一个空树。 相关概念 节点:上图中1-8都是节点 根节点:1是根节点,没有父节点 双亲:1是2/3/4的双亲,2是5/6的双亲 兄弟:2/3/4...

编程副队长 1100

Vivado 2018.3 SDK烧写bin文件避坑指南:如何解决QSPI模式下的常见错误

本文深入解析了在Vivado 2018.3 SDK中为Zynq FPGA烧写QSPI Flash时遇到的常见问题,特别是启动模式为QSPI时出现的警告与失败。文章从Zynq启动机制入手,提供了修改FSBL代码以强制使用JTAG模式、调整硬件配置等核心解决方案,并分享了实战案例与最佳实践,帮助开发者高效避坑,确保bin文件烧写成功。

arduino9maker的博客 390

子节点带有指向父节点指针的二叉树拷贝算法

若二叉树中带有指向父节点指针,则二叉树的拷贝算法需要使用后续遍历,代码如下: struct Node { int data; Node* lchild; //左孩子指针 Node* rchild; //右孩子指针 Node* parent; //父节点指针 }; Node* copy(Node *p)//拷贝二叉树函数 { if (NULL == p) r...

qq_15054345的博客 980

AS7341光谱传感器实战:如何用STM32F103精准测量环境色温(附完整代码)

本文详细介绍了如何使用STM32F103驱动AS7341光谱传感器实现环境色温的精准测量。从硬件连接到驱动开发,再到色温计算算法的实现,提供了完整的代码示例和优化建议,帮助开发者在智能照明、农业监测等领域快速应用AS7341进行高精度色温检测。

weixin_30352645的博客 478

innovus:streamOut gds中pg text带有冒号(:)解决办法

streamOut后查看layout,发现部分PG text后面加了冒号,比如VDD: / VSS:。当然,这个问题还有另一种解决办法,就是通过脚本给terminal打text再streamOut。我正在「拾陆楼」和朋友们讨论有趣的话题,你⼀起来吧?

m0_61544122的博客 1192

《大话数据结构》笔记:第六章

目录六、树6.1.1 度(Degree)6.2.2节点间关系6.2.3 其他概念6.3 具体操作6.4树的存储结构6.4.1父节点表示法6.4.2 子节点表示法6.4.2 兄弟节点表示法6.5二叉树(重点)6.5.1特点6.5.2 特殊二叉树6.6二叉树的特性6.7二叉树存储结构6.7.1顺序存储结构6.7.2二叉链表6.8遍历二叉树前序遍历中序后序层序6.9建立二叉树6.10线索二叉树6.11树...

weiruoxingguang的博客 377

ADS基础教程20 - 电磁仿真(EM)参数化仿真

一、引言二、参数化设置1.参数定义2.参数赋值3.创建EM模型和符号四、总结1)在Layout视图,菜单栏中选中EMComponent2)在弹出的参数编辑框中,采用标称/扰动()设计。使用子网(Subnetwork)参数。这些方法是互斥的,在为单个组件定义参数时不能将它们组合起来。标称/扰动(Nominal/Perturbed)参数使用平面化布局场景,并且只包含端口和原始图形形状(如多边形、矩形和圆形)时定义。

XZ_ZC的博客 4031

嵌入式面试

嵌入式实时多线程操作系统,基本属性之一是支持多任务。一个处理器核心在某一时刻只能运行一个任务。在RTT中,任务通过线程实现。RTT主要采用C语言编写,浅显易懂,方便移植。它把面向对象的设计方法应用到实时系统设计中,使得代码风格优雅、架构清晰、系统模块化并且可裁剪性非常好。相较于Linux操作系统,RTT体积小,成本低,功耗低,启动速度快,实时性高,占用资源小,非常适用于资源受限的场合。物联网操作系统是指以操作系统内核为基础,包括文件系统、图形库等较为完整的中间件组件,具备低功耗、安全的软件平台。

Caramel_biscuit的博客 1074

Unity 面试篇|(二)Unity基础篇 【全面总结 | 持续更新】

在主线程运行的同时开启另一段逻辑处理,来协助当前程序的执行,协程很像多线程,但是不是多线程,Unity的协程实在每帧结束之后去检测yield的条件是否满足。一个程序在运行(执行)的过程中所需要的硬件和软件环境运行时的主要作用是提供程序运行所需要的环境和基础设施,通过为程序提供内存分配、线程管理、类型检查、对象实例化和垃圾回收等操作来支持程序的运行公共语言运行时(Common Language Runtime,CLR)是整个.NET框架的核心,它为.NET应用程序提供了一个托管的代码执行环境。

游戏开发小Y的博客 13万+

使用Gitlab 搭建私有镜像仓库(外置Nginx)

为了让团队尽快使用Gitlab的CI进行工作,必须做好Gitlab的初始化工作,大家都知道拉取镜像由于某些原因,会比较慢,因此构建自己的私有镜像仓库就成为了关键的一步。当然私有镜像仓库的搭建有很多种方式,这里直接使用Gitlab提供的镜像仓库功能。一揽子解决方案比较香。gitlab真的越来越好用了,有了这个镜像仓,也没必要再使用三方的镜像仓库了。Devops 真好用!

网尘的技术专栏 1829

一棵树的存储结构可以采用双亲表示法,即父指针数组表示法。试给出相应的类定义。其中,每个树结点包含两个成员:数据域data和双亲指针parent;树则有一个树结点数组NodeList[MaxSize]

一棵树的存储结构可以采用双亲表示法,即父指针数组表示法。试给出相应的类定义。其中,每个树结点包含两个成员:数据域data和双亲指针parent;树则有一个树结点数组NodeList[MaxSize],MaxSize表示该数组的最大结点个数,size是当前结点个数,current指示最近操作结点位置,即当前指针。 【解答】 下面给出用双亲(父指针)表示的树和树结点类定义。 template <class Type> class Tree; template <class Ty..

weixin_41290863的博客 931

【网络安全 | 渗透工具】密码字典生成工具crunch、cupp安装使用教程

Crunch是一个常用的密码破解工具,它可以根据指定的字符集、长度和模式生成各种可能的密码并进行暴力破解。同时,Crunch可以将生成的密码保存到文件中,以便在破解工具中使用。在进行渗透测试时,手动收集密码字典是十分耗时的,利用密码字典生成工具可以大幅缩短时间,提高爆破宽度。

等风来 8425

Java学习笔记之集合(六):TreeSet集合的实现原理

package com.collection.set; import java.util.HashSet; import java.util.Set; import org.junit.Test; /* 集合的体系: ----| Collection: 单例集合的根接口 -------| List:实现了List接口的集合类,具备的特点:有序,可重复; ----------| ArrayList:底层是维护了一个Object数组实现的;特点:查询速度快,增删慢; ----------|...

aotongkeji的博客 207

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

TreeMap源码分析

/** The comparator used to maintain order in this tree map, or null if it uses the natural ordering of its keys. @serial*///自然排序private final Comparator<? super K> comparator;//根节点private tr...

bixiu3460的博客 147

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

JAVA集合Set(TreeSet)

可以去除重复存取顺序不一致没有带索引的方法,所以不能使用普通for循环遍历,也不能通过索引来获取,删除Set集合里面的元素。

ZeusHeng的博客 319

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

【Set的实现类【TreeSet、HashSet】、数据结构-树】

【Set的实现类【TreeSet、HashSet】、数据结构-树】

Continueye的博客 365

JAVA进阶-set,Comparable排序,数据结构-树

简单介绍set,Comparable排序,数据结构-树

qinqin__的博客 712

传统vsAI:哈夫曼树实现效率对比

对于需要快速验证想法的场景,现在我会优先考虑用自然语言描述需求获取基础实现,再针对性优化,这比传统开发模式省力太多了。:在平台内置编辑器运行后,发现解码函数有个小bug,用对话反馈"解码时遇到索引越界错误",AI立刻给出了修复版本。:对同一段文本编码测试,两种实现速度相当,但AI版本的内存占用少15%,因其使用了更合适的数据结构。:编写合并节点的主循环时,容易漏掉空指针检查,导致运行时崩溃,我花了半小时才定位到边界条件问题。:仍建议手动实现理解原理,但可用AI生成作为参考答案,对比优化自己的代码。

BlackironPanther23的博客 937

优先队列的自然排序以及加入Comparator接口的实现

优先队列(PriorityQueue)的实现: 一,PriorityQueue的特性 PriorityQueue是一种比较特殊的队列数据结构,传统的队列复合(FIFO)先进先出原则,而PriorityQueue是以数据的优先级进行存储; PriorityQueue类在Java1.5中引入并作为 Java Collections Framework 的一部分。PriorityQueue是基于优...

fath_kevin的博客 2778

Hanlp自然语言处理中的词典格式说明

使用过hanlp的都知道hanlp中有许多词典,它们的格式都是非常相似的,形式都是文本文档,随时可以修改。本篇文章详细介绍了hanlp中的词典格式,以满足用户自定义的需要。 基本格式 词典分为词频词性词典和词频词典。 1、词频词性词典(如CoreNatureDictionary.txt) (1)每一行代表一个单词,格式遵从[单词] [词性A] [A的频次] [词性B] [B的频次] ......

adnb34g的博客 1595

关于Set接口中TreeSet实现类、Map接口和HashMap实现类、数据结构、自然排序(比较器)和自定义排序(指定比较器)的课程笔记

指针JAVA培训day_20 TreeSet© (1).底层数据结构:二叉树 (2).父接口: SortedSet(I),可排序的集合 (3).特点: a.TreeSet是可排序的,最常用的中序遍历 (4).TreeSet的存储过程 a.最开始和根节点进行比较 - 通过compareTo()进行比较 b.如果比根大,则存储在右边 c.如果比根小,则存储在左边 d.如果和根一样大,则不存入 递归方式写add方法 //定义一个内部方法 private void addNode(Node node , T t

qq_46053369的博客 348
上一篇: 指针浅析
下一篇: 静态链表解析
且听风吟0618
博客等级 码龄11年 150粉丝 25原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值