数据结构(八)----树

目录

一.树的逻辑结构

1.双亲表示法(顺序存储)

2.孩子表示法(顺序+链式存储)

3.孩子兄弟表示法(链式存储)

二.树的遍历

1.先根遍历

2.后根遍历

3.层次遍历

三.森林的遍历

1.森林的先序遍历

2.森林的中序遍历

四.哈夫曼树

1.带权路径长度

2.构造哈夫曼树

3.哈夫曼编码


一.树的逻辑结构

树是n (n\geq0) 个结点的有限集合,n=0时,称为空树,这是一种特殊情况。

在任意一棵非空树中应满足:

1)有且仅有一个特定的称为根的结点。

2)当n>1时,其余结点可分为m (m>0) 个互不相交的有限集合T1,T2.…. Tm,其中每个集合本身又是一棵树,并且称为根结点的子树。

1.双亲表示法(顺序存储)

除了根节点之外,其它节点具有唯一的父节点,所以每个节点除了保存数据外,还会保存指向双亲的“指针”。

如下图所示,-1表示没有双亲,B,C,D的双亲为0号节点。

注:新增数据元素,无需按逻辑上的次序存储

#define MAX_TREE_SIZE 100    //树中最多结点数
typedef struct{    //树的结点定义
    ElemType data;    //数据元素
    int parent;    //双亲位置域
}pTNode;

typedef struct{     //树的类型定义
    PTNodenodes[MAX_TREE_SIZE];    //双亲表示 
    int n;    //结点数
}PTree;

•插入新节点:写入新节点的值,并记录其与双亲的关系。

 注:新增数据元素,无需按逻辑上的次序存储

•删除节点:

方案一:删除元素后,将其双亲"指针"设为"-1"。

方案二:将尾部的数据移动到删除元素的位置。

哪个方案更好呢?

若要删除的节点不是叶子节点,那么需要将其子孙节点全部删除,这就需要依次遍历,查找他的孩子节点。若采用第一种方案,则遍历时会出现空数据,会导致遍历速度下降。

在二叉树的顺序存储中,二叉树的节点编号与完全二叉树对应:

i的左孩子---2i

i的右孩子---2i+1

i的父节点---\left \lfloor i/2 \right \rfloor

可以看到,节点的编号不仅反映了存储位置,也隐含了节点之间的逻辑关系。

忘记了可以看看:二叉树

2.孩子表示法(顺序+链式存储)

顺序存储各个节点,每个结点除了保存数据域外,还保存了指向第一个孩子的指针。

struct CTNode {
    int child;              // 孩子结点在数组中的位置
    struct CTNode *next;    // 下一个孩子
};
typedef struct {
    ElemType data;      
    struct CTNode *firstChild;    // 第一个孩子
} CTBox;
typedef struct {
    CTBox nodes[MAX_TREE_SIZE];
    int n,r;               // 结点数和根的位置
} CTree;
3.孩子兄弟表示法(链式存储)
typedef struct csNode{
    ElemType data;     //数据域
    struct CSNode *firstchild,*nextsibling;   
    //firstchild指向第一个孩子,nextsibling指向第一个孩子的右兄弟
}CSNode,*CSTree; 

如下图所示:

A的第一个孩子是B,A的左指针指向B,B的右兄弟为C,所以B的右指针指向C,C的右兄弟是D,所以C的右指针指向D,其他依此类推。

这就实现了树到二叉树的转化。

二叉树到树转换的逻辑与上面反过来就行:

补充:森林和二叉树的相互转换

1.首先将森林里的每一棵树转换为二叉树:

2.将根节点看作兄弟结点,通过右指针指向:

二叉树转换为森林的逻辑反过来即可:

二.树的遍历

1.先根遍历

树的先根遍历与二叉树的先根遍历原理相同。

将其对应的二叉树画出来,可以观察到,树的先根遍历序列与这棵树相应二叉树的先序序列相同。

2.后根遍历

转换为对应二叉树后,树的后根遍历序列与这棵树相应二叉树的中序序列相同。

3.层次遍历

与二叉树的层次遍历相同:

① 若树非空,则根节点入队

② 若队列非空,队头元素出队并访问,同时将该元素的孩子依次入队

③ 重复②直到队列为空

注:树的层次遍历可以称为广度优先遍历;相对地,先根遍历和后根遍历可以称为深度优先遍历。

三.森林的遍历

1.森林的先序遍历

若森林为非空,则按如下规则进行遍历:

1.访问森林中第一棵树的根结点。

2.先序遍历第一棵树中根结点的子树森林。

3.先序遍历除去第一棵树之后剩余的树构成的森林。

如下图所示,① 先访问B,在访问第一棵树中根节点的子树森林,即以E,F为根节点的森林。② 递归到第一步,访问子树森林中第一棵树的根节点,即E,再先序遍历E的两棵子树森林K,L。③ 先序遍历除去第一棵树之后剩余的树构成的森林,即F。

其等同于依次对各个树进行先根遍历,最后结果如下:

或者可以将森林先转换为二叉树,森林的先序遍历序列和二叉树的先序遍历序列也是相同的。

2.森林的中序遍历

1.中序遍历森林中第一棵树的根结点的子树森林。

2.访问第一棵树的根结点。

3.中序遍历除去第一棵树之后剩余的树构成的森林。

对森林的中序遍历,效果等同于依次对各个树进行后根遍历,结果如下:

或者可以把他转化为对应的二叉树,森林的中序遍历,效果等同于对应二叉树的中序遍历

总结:
森林先序遍历效果等同于森林中各个树先根遍历,也等同于对二叉树先序遍历

森林中序遍历效果等同于森林中各个树后根遍历,也等同于对二叉树中序遍历

四.哈夫曼树

1.带权路径长度

结点的权:有某种现实含义的数值(如:表示结点的重要性等),下图所示,每一个结点都有对应的权值。

结点的带权路径长度:从树的根到该结点的路径长度(经过的边数)与该结点上权值的乘积。

例如,权值为3的结点:从根结点到权值为3的结点的路径长度为3,所以其带权路径长度为3*3=9.

树的带权路径长度:树中所有叶结点的带权路径长度之和(WPL,Weighted Path Length)

注:树的路径长度是指根到每个结点的路径长的总和,根到每个结点的路径长度的最大值应是树的高度-1,注意与这里做区别。

例如:

第一个图中,叶子结点有4个,根到各个结点的路径长度是2,所以这棵树的带权路径长度:

2*1+2*3+2*4+2*5=26

第二个图中,叶子结点有4个,根到1,3,4,5的路径长度为3,3,2,1,所以带权路径长度:1*5+2*4+3*1+3*3=25

第三个图同理,带权路径长度为:1*5+2*4+3*1+3*3=25

第四个图的带权路径长度为:1*1+2*3+3*5+3*4=34

哈夫曼树:在含有n个带权叶结点的二叉树中,其中带权路径长度(WPL)最小的二叉树称为哈夫曼树,也称最优二叉树。上面计算的中间两棵树都是哈夫曼树。

2.构造哈夫曼树

(1) 将这n个结点分别作为n棵仅含一个结点的二叉树,构成森林F。

(2) 构造一个新结点,从F中选取两棵根结点权值最小的树作为新结点的左、右子树,并且将新结点的权值置为左、右子树上根结点的权值之和。

(3) 从F中删除刚才选出的两棵树,同时将新得到的树加入F中。(当然也可以选择e和b)

(4) 重复步骤2)和3),直至F中只剩下一棵树为止。

这棵树的带权路径长度WPL_{min}=1*7+2*3+3*2+4*1+4*2=31

从上面可以观察到:

• 每个初始结点最终都成为叶结点,且权值越小的结点到根结点的路径长度越大。

•n个结点,两两结合为一棵树,总共需要合并n-1次,每一次合并都会增加一个结点,所以哈夫曼树的结点总数为n+n-1=2n-1个。

•哈夫曼树中不存在度为1的结点。

•哈夫曼树并不唯一,但WPL必然相同且为最优。

3.哈夫曼编码

对于固定长度编码,每个字符需要用相等长度的二进制位表示,例如:

若用权值表示各个字母的使用次数,假设A的使用次数:10,B:8,C:80,D:2,那么其带权路径长度可以这样表示:从根节点出发,向左的路径是二进制0,向右的路径是二进制1(也可以反过来)

上面这棵树的带权路径长度为200,用A,B,C,D构造哈夫曼树,能使带权路径长度达到最小:

上面这棵树对应的A,B,C,D的编码如下:C使用了最多次,所以C的编码最简单,以这样的逻辑进行编码,那么发送的编码长度就能最小化。

这种编码方式就是可变长度编码,允许对不同字符用不等长的二进制位表示。

A的使用次数也很高,为什么不能直接将其编码为1呢?也就是A不作为叶子节点

采用这样的编码方式,若要发送CAAABD这样的字符,则发送的二进制编码为:

0 1 1 1 111 110

接收者接收到这一串二进制编码时,可能翻译为:

导致解码错误,产生歧义。

所以A,B,C,D字符只能作为叶子节点,不能当作分支节点。

上面这样的编码也可称为前缀编码,也就是没有一个编码是另一个编码的前缀。采用这种前缀码,解码时才不会产生歧义。

上图,由于A的编码是1,与B,D有相同的前缀,所以会产生解码歧义。

注:之前讲过,对于给定的叶子节点,哈夫曼树是不唯一的,所以哈夫曼编码也是不唯一的。

由于哈夫曼编码能使发送的数据长度达到最小,所以哈夫曼编码也用于数据的压缩

上图中哈夫曼编码的长度为130,加权平均长度计算得到的WPL与各编码的权值之和的比值:130/80+10+2+8=13/10

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于FPGA的数字信号处理(6)--如何确定Verilog表达式的符号

前言 尽管signed语法的使用能带来很多便利,但同时也给表达式的符号确定带来了更多的不确定性。比如一个有符号数和一个无符号数的加法/乘法结果是有符号数还是无符号数?一个有符号数和一个无符号数的比较结果是有符号数还是无符号数?等等。接…

数学建模--图论最短路径基础

1.图的定义 学过数据结构或者离散数学的小伙伴们应该知道图的概念,我在这里简单的介绍一下: 图的概念和我们理解的是很不一样的,这里的图并不是我们的生活里面的图片,而是一种表示不同的数据之间的关系,例如这里的5个…

CMMI认证--基础知识总览

最近公司研发部搞CMMI L5认证,顺便记录下培训内容。 文章目录 一、什么是CMMI二、CMMI作用三、CMMI的成熟度等级四、过程域五、此次CMMI DEV 2.0或3.0特点六、CMMI 评估1、评估方法2、客观证据3、每个过程域如何给出评分等级 七、CMMI规程文件八、CMMI L5将度量统计…

Re69:读论文 LaMDA: Language Models for Dialog Applications

诸神缄默不语-个人CSDN博文目录 诸神缄默不语的论文阅读笔记和分类 论文名称:LaMDA: Language Models for Dialog Applications ArXiv网址:https://arxiv.org/abs/2201.08239 本文介绍谷歌提出的对话大模型LaMDA,主要关注对各项指标&#x…

【C 数据结构】深度优先搜索、广度优先搜索

文章目录 【 1. DFS 深度优先搜索 】1.1 基本原理1.2 C 实现 【 2. BFS 广度优先搜索 】2.1 基本原理2.2 C 实现 【 3. 深度优先生成树、广度优先生成树 】【 4. 深度优先生成森林、广度优先生成森林 】4.1 深度优先生成森林4.2 广度优先生成森林 对存储的图中的顶点进行遍历搜…

【信号与系统杂谈 - 1】为什么拉普拉斯变换有收敛域而傅里叶变换没有

这个问题是我在推导傅里叶变换的时移特性公式和拉普拉斯变换的延时特性公式时发现的(即拉氏变换总是需要考虑收敛域的原因) 援引知乎上的回答解释

12_Scala_package

文章目录 Scaal面向对象编程1.回顾Java2.package可以多次声明3.设置作用域,设置上下级4.包可以当作对象使用5.import6.Scala用_取代Java *7.导入多个包8.屏蔽类9.类起别名10.import的规则11.有些包无需导入 Scaal面向对象编程 Scala是一门完全面向对象语言&#xf…

C# winform 漂亮的日期时间控件

源代码下载: https://download.csdn.net/download/gaoxiang19820514/89242240 效果图 在 HZH-Controls控件 基础上修改的日期控件 因为HZH_Controls控件 中的日期控件太大了, 我的程序中需要多个日期时间的控件放不下,主题是绿色的&#…

Springboot+Vue项目-基于Java+MySQL的校园疫情防控系统(附源码+演示视频+LW)

大家好!我是程序猿老A,感谢您阅读本文,欢迎一键三连哦。 💞当前专栏:Java毕业设计 精彩专栏推荐👇🏻👇🏻👇🏻 🎀 Python毕业设计 &…

力扣刷题Day2

题目链接: 24. 两两交换链表中的节点 - 力扣(LeetCode) 效果: 解题思路: 给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。 注意不可以只是单纯的改变节点内部的值,而…

Docker--compose概述与部署

目录 一、概述 1. Compose简介 1.1 docker compose常用命令 1.2 Compose配置常用字段 2. YAML简介 2.1 YAML支持的数据结构 2.2 YML文件编写注意事项 2.3 Docker Compose文件结构 3. Docker-Compose安装 ​编辑 4.docker Compose撰写nginx 镜像 1. 准备环境 ​编辑…

苹果和OpenAI再续前缘,iOS 18会是颠覆级的吗?|TodayAI

据彭博社最新报道,苹果公司已经与人工智能领域的先锋企业OpenAI重启了对话,双方目前正在讨论一项可能的合作,以将OpenAI的生成式人工智能技术整合到苹果即将推出的iOS 18操作系统中。这一举措表明,苹果正加速其在人工智能技术上的…

【EI会议|稳定检索】2024年传感技术与图像处理国际会议(ICSTIP 2024)

2024 International Conference on Sensing Technology and Image Processing 一、大会信息 会议名称:2024年传感技术与图像处理国际会议会议简称:ICSTIP 2024收录检索:提交Ei Compendex,CPCI,CNKI,Google Scholar等会议官网:htt…

final原理

文章目录 1. 设置 final 变量的原理2. 获取 final 变量的原理 1. 设置 final 变量的原理 理解了 volatile 原理,再对比 final 的实现就比较简单了 public class TestFinal {final int a 20; }字节码 0: aload_0 1: invokespecial #1 // Method java/lang/Object…

数组 Leetcode 704 二分查找/Leetcode 59 螺旋矩阵/Leetcode 203移除链表元素

数组 Leetcode 704 二分查找 Leetcode 704 学习记录自代码随想录 二分法模板记忆&#xff0c;数值分析中牛顿迭代法 class Solution { public:int search(vector<int>& nums, int target) {int left 0, right nums.size()-1;// 是否需要等于号&#xff0c;假设…

SpringCloud之OpenFeign

学习笔记&#xff1a; 官网地址&#xff1a;https://docs.spring.io/spring-cloud-openfeign/docs/current/reference/html/#spring-cloud-feign 源码&#xff1a;https://github.com/spring-cloud/spring-cloud-openfeign 1、概念总结 OpenFeign是一个声明式的Web服务客户端…

MySql-日期分组

一、分别统计各时间各类型数据条数 数据库的 request_time字段 数据类型&#xff1a;timestamp 默认值&#xff1a;CURRENT_TIMESTAMP 例子&#xff1a; 2024-01-26 08:25:48 原数据&#xff1a; 1、将数据按照日期&#xff08;年月日&#xff09;形式输出 按照request_…

【人工智能基础】聚类实验分析

实验环境&#xff1a;anaconda、jupyter notebook、spyder 实现用到的类库&#xff1a;numpy、matplotlib、scikit-learn k均值聚类&#xff08;K-MEANS&#xff09; k均值聚类的原理&#xff1a; 选定k个聚类中心把数据集中距离聚类中心i最近的点都归属到一个簇根据每个簇中…

debian配置四叶草输入法

效果展示 一、前言 在linux下体验比较好的输入法只有两款&#xff1a;搜狗输入法、四叶草输入法。 ubuntu下可以成功配置搜狗输入法&#xff0c;但debian下从来没有成功过。 今天在用fcitx5 四叶草时发现VNC远程输入法会失灵&#xff0c;于是改用了ibus 四叶草&#xff0c…

C# wpf 运行时替换方法实现mvvm自动触发刷新

文章目录 前言一、如何实现&#xff1f;1、反射获取属性2、定义替换方法3、交换属性的setter方法 二、完整代码1、接口2、项目 三、使用示例1、倒计时&#xff08;1&#xff09;、继承ViewModelBase&#xff08;2&#xff09;、定义属性&#xff08;3&#xff09;、属性赋值&am…