【数据结构入门精讲 | 第十一篇】一文讲清树

在上一篇中我们进行了排序算法的专项练习,现在让我们开始树的知识点讲解。

在这里插入图片描述

目录

    • 二叉搜索树
    • 二叉排序树
    • 哈夫曼树
    • 折半查找判定树
    • kruskal算法、prim算法、最小生成树
    • 完全二叉树

树是一种非线性的数据结构,也是一种表示一对多关系的数据结构,它由若干个节点(Node)和连接这些节点的边(Edge)组成。树有很多应用,如用于实现文件系统、数据库索引和编译器等。

下面是树的一些常见概念及其相关知识点:
1.根节点(Root):树的最顶层节点,它没有父节点。
2.叶子节点(Leaf):没有子节点的节点。
3.父节点(Parent):如果一个节点有子节点,则该节点称为其子节点的父节点。
4.子节点(Child):一个节点的直接后继节点称为其子节点。
5.节点的度(Degree):一个节点拥有的子节点数称为其度。
6.树的度(Degree):树中所有节点度的最大值。
7.深度(Depth):根节点到该节点的路径长度。
8.高度(Height):该节点到叶子节点的最长路径长度。
9.子树(Subtree):以一个节点为根节点的子树是由该节点及其所有后代节点组成的树。
10.兄弟节点(Sibling):拥有同一个父节点的节点互为兄弟节点。
11.前序遍历(Pre-order Traversal):首先访问根节点,然后依次递归遍历左子树和右子树。

可以每个节点的左侧画一个点,然后逆时针画一条线,经过点就输出点对应的节点(推荐)

在这里插入图片描述
中序遍历(In-order Traversal):先递归遍历左子树,然后访问根节点,最后递归遍历右子树。

可以每个节点的下方画一个点,然后逆时针画一条线,经过点就输出点对应的节点(推荐)

在这里插入图片描述
后序遍历(Post-order Traversal):先递归遍历左子树和右子树,然后访问根节点。

可以每个节点的右侧画一个点,然后逆时针画一条线,经过点就输出点对应的节点(推荐)

在这里插入图片描述
12.层序遍历(Level-order Traversal):从上到下,从左到右依次访问每一层的所有节点。

13.二叉树(Binary Tree):每个节点最多只有两个子节点的树。

14.满二叉树(Full Binary Tree):除叶子节点外,每个节点都有两个子节点的二叉树。

15.完全二叉树(Complete Binary Tree):除了最后一层外,其他层的节点数都是满的,最后一层的节点全部靠左排列的二叉树。

16.平衡二叉树(Balanced Binary Tree):左右子树的高度差不超过1的二叉树。

在这里插入图片描述

二叉搜索树

二叉查找树(Binary Search Tree),(又:二叉搜索树,二叉排序树)它或者是一棵空树,或者是具有下列性质的二叉树: 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值; 它的左、右子树也分别为二叉排序树。
AVL树(平衡二叉树/二叉查找树)

AVL树是一种自平衡的二叉搜索树,它得名于其发明者G. M. Adelson-Velsky和E. M. Landis。AVL树的特点是任何节点的两个子树的高度最多相差1,这使得AVL树在插入和删除节点时能够保持较好的平衡性,从而保证了查找操作的效率。

在AVL树中,每个节点都会存储一个平衡因子,该平衡因子是其左子树的高度与右子树的高度之差。对于任何AVL树中的每个节点,其平衡因子的值只可能是-1、0或1。如果在进行插入或删除操作后,任何节点的平衡因子的绝对值超过1,则需要通过旋转操作来恢复树的平衡。旋转操作主要有以下几种:

  1. 单右旋(LL旋转):当某个节点的左子树比右子树高2个单位,并且左子树的左子树比左子树的右子树高时进行。
  2. 单左旋(RR旋转):当某个节点的右子树比左子树高2个单位,并且右子树的右子树比右子树的左子树高时进行。
  3. 左-右双旋转(LR旋转):当某个节点的左子树比右子树高2个单位,并且左子树的右子树比左子树的左子树高时进行。这个操作是先对问题节点的左子树进行单左旋,然后对问题节点进行单右旋。
  4. 右-左双旋转(RL旋转):当某个节点的右子树比左子树高2个单位,并且右子树的左子树比右子树的右子树高时进行。这个操作是先对问题节点的右子树进行单右旋,然后对问题节点进行单左旋。

通过这些旋转操作,AVL树在每次插入或删除后都能迅速恢复平衡,从而保证了最坏情况下的操作时间复杂度为O(log n),其中n是树中节点的数量。这使得AVL树在需要频繁插入或删除的场景中表现良好,如数据库索引和内存中的查找表。

性质:任何二叉搜索树中同一层的结点从左到右是有序的(从小到大)。

二叉搜索树每个结点的值均大于其左子树上任一节点的值

二叉搜索树每个结点的值均小于其右子树上任一节点的值

如何构建AVL树呢?
遵循如下原则:
1. 从根节点开始查找插入位置。
2. 如果插入元素小于当前节点值,则在当前节点的左子树中查找插入位置;如果插入元素大于当前节点值,则在当前节点的右子树中查找插入位置。
3. 沿着插入路径更新每个节点的平衡因子,并检查是否需要进行旋转操作来恢复平衡。


比如说有34 23 15 98 115 107
先插入
      34
     23
    15
此时左子树高度为2,右子树高度为0,二者相差超过1
所以把第二个大的(即23)往上提
得到
    23
  15 34
再加98
得到

    23
  15 34
       98

再加115
得到

    23
  15 34
       98
         115
此时高度差又大于1,所以以34 98 115为单元再把第二个大的(即98)往上提
得到
    23
  15 98
    34 115  
对于23 98 34又不满足递增序列,因此将第二个大的(即34)作为根节点得到
    34
  23  98
15      115
加107得到
    34
  23  98
15  107 115   


删除操作
删叶节点之后,再平衡即可

二叉排序树

如何创建二叉排序树

假设

39 11 68 46 75 23 71 8 64 34

先放置39
1139小,放左边
   39
 11
6839大,放右边
   39
 11  68
4639大,放39右边,但比68小,放68左边
   39
 11  69
    46
    
由此最终得到
            39
       11        68
     8   23    46  75
          34 71 86
          
验证:如果排序完的二叉树在中序时是递增的,则说明排序无误



插入一个元素之前需要判断是否存在该元素
比如需要插入23,但23已存在,所以不需要插入23
再比如插入52,还是一样,与根节点做比较
52大于39,放在在39右边;比68小,放在68左边;比46大,放在46右边;比86小,所以是86的左子树


如何删除一个元素
对于删除叶子节点,直接删除就行
对于删除没有左子树的节点,即23,用该节点的右子树的根节点代替
即用34代替23
对于删除没有右子树的节点,用该节点的左子树的根节点代替
对于删除有左右子树的节点,选择它的左子树中最大的或右子树中最小的去替换

哈夫曼树

给定N个权值作为N个叶子结点,构造一棵二叉树,若该树的带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman Tree)。哈夫曼树是带权路径长度最短的树,权值较大的结点离根较近。

二叉排序树(Binary Search Tree,BST),也称为二叉搜索树或有序二叉树,是一种特殊的二叉树结构。它具有以下性质:

1. 对于二叉排序树的每个节点,其左子树中的所有节点的值都小于该节点的值,而右子树中的所有节点的值都大于该节点的值。

2. 对于二叉排序树的任意节点,它的左子树和右子树也是二叉排序树。

由于这些性质,二叉排序树可以用于高效地实现查找、插入和删除操作。

对于查找操作,可以按照以下步骤进行:

1. 从根节点开始,比较要查找的值与当前节点的值。

2. 如果要查找的值等于当前节点的值,则查找成功。

3. 如果要查找的值小于当前节点的值,则继续在左子树中查找。

4. 如果要查找的值大于当前节点的值,则继续在右子树中查找。

5. 如果到达叶子节点仍然没有找到,则查找失败。

对于插入操作,可以按照以下步骤进行:

1. 从根节点开始,比较要插入的值与当前节点的值。

2. 如果要插入的值小于当前节点的值,并且当前节点的左子树为空,则将新节点插入为当前节点的左子节点。

3. 如果要插入的值大于当前节点的值,并且当前节点的右子树为空,则将新节点插入为当前节点的右子节点。

4. 如果要插入的值等于当前节点的值,则不进行插入操作(可以根据需求进行处理,如插入重复值的处理)。

5. 如果要插入的值小于当前节点的值,并且当前节点的左子树不为空,则继续在左子树中递归进行插入操作。

6. 如果要插入的值大于当前节点的值,并且当前节点的右子树不为空,则继续在右子树中递归进行插入操作。

对于删除操作,可以按照以下情况进行处理:

1. 如果待删除的节点为叶子节点(没有子节点),则直接删除该节点。

2. 如果待删除的节点只有一个子节点,则将该子节点替代待删除节点的位置。

3. 如果待删除的节点有两个子节点,则找到其右子树中的最小节点(或左子树中的最大节点),将其值赋给待删除节点,然后再递归地删除该最小节点。

比如给定

19 21 2 3 6 7 10 32

求如何构造哈夫曼树

解:
先找最小的两个,即2 3,合并为5
    5
   2 3
现在序列变为19 21 5 6 7 10 32
再找最小的两个,即5,6,合并为11
   11
 5   6
2 3
现在序列变为19 21 11 7 10 32
由此不断循环得到
    100           第0层
 40       60      第1层
19 21    28  32   第2层
      11  17      第3层
     5 6 7 10     第4层
    2 3           第5层
    
要计算WPL值,只需要将每个叶节点的值乘以层数再相加就行了
(19+21+32)*2+(6+7+10)*4+(2+3)*5=261


哈夫曼编码是什么呢?
左为0.右为1

在这里插入图片描述
对于9而言,它的哈夫曼编码就是10;对于15而言,它的哈夫曼编码就是01。

哈夫曼树是不唯一的,可能有多种构造形态,但是哈夫曼树的WPL一定是唯一且最小的

折半查找判定树

如何构建呢?
比如给出3 4 5 7 24 30 42 54 63 72 95
先给他们编号
3    4    5    7   24   30   42   54   63   72    95
(1)  (2)  (3)  (4)  (5)  (6)  (7)  (8)  (9)  (10)  (11)
接着将(1+11)/2=6.5-》向下取整为6
将编号为6的30作为根节点
接下来如何找左节点和右节点呢?
对于6的左边
(1+5)/2=3
将编号为3的5作为左节点,同理将编号为9的63作为右节点
得到
     30
    5  63
 同理对5的左边处理,将(1+2)/2=1.5-》1
 故将编号为1的3作为5的左节点
 同理将(4+5)/2=4.5-》4
 故将编号为4的7作为5的右节点
      30
    5    63
  3  7  
当3和5之间只有4时,因为4在3的右边,所以将其作为3的右子树
        30
    5    63
  3  7  
   4
同理24作为7的右子树

在这里插入图片描述
黄色的为失败节点,比如说要查找-1,查找到第三层0的时候,0左边没节点,所以查找不到0。

所以要查找-1,只要查找3次

而查找8,只要查找两次就行

折半查找判定树的判断

  • 如果根节点左子树和右子树的节点个数一样,则左子树和右子树的结构一样

    所以下图就不是折半查找判定树

在这里插入图片描述

  • 如果根节点左边少、右边多(比如1 2 3(根节点) 4 5 6)

    则对于任意的根节点,都是左边少右边多

或者根节点左边多右边少:则对于任意的根节点,都是左边多右边少

比如

在这里插入图片描述
此时根节点左边节点少,右边节点多。对于根节点的左节点,它的左边多右边少,所以这就不是折半查找判定树。

下图的根节点都满足左边多右边少,所以下图是折半查找判定树。

在这里插入图片描述

kruskal算法、prim算法、最小生成树

Kruskal算法和Prim算法都是用于求解最小生成树(Minimum Spanning Tree)的经典算法。

1.Kruskal算法:

  • Kruskal算法基于贪心策略,按照边的权值从小到大的顺序选择边,并且要求选择的边不形成环路。
  • 算法步骤:
    1. 将图中的所有边按照权值从小到大进行排序。
    2. 初始化一个空的最小生成树。
    3. 依次从排好序的边中选取权值最小的边,如果该边的两个端点不在同一个连通分量中,则将该边加入最小生成树中,并将两个端点合并到同一个连通分量中。
    4. 重复步骤3直到最小生成树中的边数等于总节点数减一,或者所有边都考察完毕。
  • 时间复杂度:O(ElogE),其中E是边的数量。

2.Prim算法:

  • Prim算法也是一种贪心算法,它从一个初始顶点开始,逐步扩展生成最小生成树的各个部分。每次选择当前最小权值的边,并且使得生成树的顶点集合逐渐增大。
  • 算法步骤:
    1. 随机选择一个顶点作为初始顶点,加入最小生成树中。
    2. 在所有与最小生成树中的顶点相邻的边中,选择权值最小的边,将其连接的顶点加入最小生成树中。
    3. 重复步骤2直到最小生成树中的顶点数等于总节点数,或者所有顶点都被考虑完毕。
  • 时间复杂度:O(V^2),其中V是顶点的数量。如果使用优先队列来优化选取最小权值边的过程,则时间复杂度可以降低到O((V+E)logV)。

这两种算法都可以用来解决无向连通图的最小生成树问题,但Kruskal算法更适用于稀疏图,而Prim算法更适用于稠密图。在实际应用中,选择合适的算法取决于具体的问题和图的规模。

简要来说
kruskal算法就是将边的权值依次排列
比如
a--b 3
a--c 4
接着按权值由低到高进行连线
但不能形成回路
如果连接该线之后形成回路,则找下一条边


prim算法就是从第一个点开始,找离其最近的点,相连
接着将两个点看作整体,找离该整体最近的点,相连
但不能连成环,如果连成环就找第二近的点
  1. 最小生成树(Minimum Spanning Tree,简称MST)是在一个连通加权无向图中找到一棵包含图中所有顶点的生成树,使得树的所有边的权值之和最小。

最小生成树具有以下特性:

包含图中的所有顶点。
是一棵树,即没有环路。
权值之和最小。

最小生成树在实际应用中有着广泛的应用,比如在网络设计中用于构建成本最小的通信网络、在电力传输中用于构建最优的输电线路等。

常见的求解最小生成树的算法包括Kruskal算法和Prim算法。这两种算法在前面已经进行了介绍。

最小生成树有可能是不唯一的,但权重和是一样的

在这里插入图片描述

完全二叉树

完全二叉树(Complete Binary Tree)是一种特殊的二叉树结构,它除了最后一层外,其他所有层的节点都必须填满,并且最后一层的节点从左到右依次填入,不能有间隔。

完全二叉树具有以下性质:

若完全二叉树的深度为h,则该完全二叉树的节点数目在[2^(h-1),2^h - 1]之间。
对于任意节点i,其左子节点的索引为2i,右子节点的索引为2i+1。
对于任意节点i,如果i > 1,则其父节点的索引为i/2取整。

相比于一般的二叉树,完全二叉树的特殊性在于它的结构更加紧凑。这个特性在某些场景下非常有用,例如构建堆数据结构、优先队列等。

判断一个二叉树是否为完全二叉树可以通过以下方式进行:

层序遍历二叉树,从左到右逐层遍历每个节点。
如果遍历到一个节点为空,则后续遍历的节点都应为空。
如果遍历到一个节点不为空,但其左子节点为空或右子节点为空,则该树不是完全二叉树。

以上就是树的全部知识点,下一篇文章中我们将进行树的专项练习。

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

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

相关文章

Flink CDC 1.0至3.0回忆录

Flink CDC 1.0至3.0回忆录 一、引言二、CDC概述三、Flink CDC 1.0:扬帆起航3.1 架构设计3.2 版本痛点 四、Flink CDC 2.0:成长突破4.1 DBlog 无锁算法4.2 FLIP-27 架构实现4.3 整体流程 五、Flink CDC 3.0:应运而生六、Flink CDC 的影响和价值…

Python 数据分析 Matplotlib篇 plot设置线条样式(第2讲)

Python 数据分析 Matplotlib篇 plot设置线条样式(第2讲)         🍹博主 侯小啾 感谢您的支持与信赖。☀️ 🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ�…

算法基础之完全背包问题

完全背包问题 核心思想:集合表示: f[i][j]表示前i种物品 总容量不超过j的最大价值 求f[i][j]时 分为选0、1、2……n个第i种物品 n种情况 每种情况为 f[i][j-kv] (取k个第i种物品) 即f[i][j] max(f[i-1][j] , f[i-1][j-v]w,f[i-1][j-2v]2w….f[i-1][j-k…

【自用】Ubuntu20.4从Vivado到ddr200t运行HelloWorld

【自用】Ubuntu20.4新系统从输入法到ddr200t运行HelloWorld 一、编辑bashrc二、Vivado2022.2安装三、编译蜂鸟E203自测样例1. 环境准备2. 下载e203_hbirdv2工程文件3. 尝试编译自测案例1. 安装RISC-V GNU工具链2. 编译测试样例 4. 用vivado为FPGA生成mcs文件1.准备RTL2.生成bit…

Centos 7.9安装Oracle19c步骤亲测可用有视频

视频介绍了在虚拟机安装centos 7.9并安装数据库软件的全过程 视频链接:https://www.zhihu.com/zvideo/1721267375351996416 下面的文字描述是安装数据库的部分介绍 一.安装环境准备 链接:https://pan.baidu.com/s/1Ogn47UZQ2w7iiHAiVdWDSQ 提取码&am…

贝叶斯球快速检验条件独立

贝叶斯球 定义几个术语,描述贝叶斯球在一个结点上的动作: 通过(pass through):从当前结点的父结点方向过来的球,可以访问当前结点的任意子结点(父->子)。从当前节点的子结点方向…

基于电商场景的高并发RocketMQ实战-NameServer内核原理剖析、Broker 主从架构与集群模式原理分析

🌈🌈🌈🌈🌈🌈🌈🌈 【11来了】文章导读地址:点击查看文章导读! 🍁🍁🍁🍁🍁🍁&#x1f3…

Prometheus介绍和安装

Prometheus介绍和安装 1. Prometheus介绍 Prometheus(普罗米修斯)是一个最初在SoundCloud上构建的监控系统。自2012年成为社区开源项目,拥有非常活跃的开发人员和用户社区。为强调开源及独立维护,Prometheus于2016年加入云原生云…

指标体系构建-03-交易型的数据指标体系

参考: 本文参考 1.接地气的陈老师的数据指标系列 2.科普 | 零售行业的数据指标体系及其含义、应用阶段 3.”人货场”模型搞懂没?数据分析大部分场景都能用! 4.一分钟读懂广告投放各计费CPM、CPC等(公式推导干货) 5.AA…

mysql 数据编译安装以及参数说明 安装包下载

目录 MySQL 官网地址官网下载源码包安装步骤修改密码 MySQL 官网地址 https://dev.mysql.com/doc/ 官网下载源码包 安装步骤 # 所需要的依赖及安装mysql的包" [rootmysql_source ~]# yum -y install ncurses ncurses-devel openssl-devel bison libgcrypt gcc gcc-c ma…

前缀和+单调双队列+贪心:LeetCode2945:找到最大非递减数组的长度

本文涉及知识点 C算法:前缀和、前缀乘积、前缀异或的原理、源码及测试用例 包括课程视频 单调双队列 贪心 题目 给你一个下标从 0 开始的整数数组 nums 。 你可以执行任意次操作。每次操作中,你需要选择一个 子数组 ,并将这个子数组用它所…

AcWing 1238. 日志统计(双指针,滑动窗口)

题目&#xff1a; 1238. 日志统计 - AcWing题库 数据范围 输入样例&#xff1a; 7 10 2 0 1 0 10 10 10 10 1 9 1 100 3 100 3输出样例&#xff1a; 1 3 思路&#xff1a;双指针 代码&#xff1a; #include<iostream> #include<cstdio> #include<cmath>…

如何从 Android 手机免费恢复已删除的通话记录/历史记录?

有一个有合作意向的人给我打电话&#xff0c;但我没有接听。更糟糕的是&#xff0c;我错误地将其删除&#xff0c;认为这是一个骚扰电话。那么有没有办法从 Android 手机恢复已删除的通话记录呢&#xff1f;” 塞缪尔问道。如何在 Android 上恢复已删除的通话记录&#xff1f;如…

LeetCode刷题--- 组合总和

个人主页&#xff1a;元清加油_【C】,【C语言】,【数据结构与算法】-CSDN博客 个人专栏 力扣递归算法题 http://t.csdnimg.cn/yUl2I 【C】 http://t.csdnimg.cn/6AbpV 数据结构与算法 http://t.csdnimg.cn/hKh2l 前言&#xff1a;这个专栏主要讲述递归递归、搜…

uniapp怎么动态渲染导航栏的title?

直接在接口请求里面写入以下&#xff1a; 自己要什么参数就写什么参数 本人仅供参考&#xff1a; this.name res.data.data[i].name; console.log(名字, res.data.data[i].name); uni.setNavigationBarTitle({title: this.name}) 效果&#xff1a;

[Linux] MySQL数据库之索引

一、索引的相关知识 1.1 索引的简介 索引是一个排序列表&#xff0c;包含索引值和包含该值的数据行的物理地址&#xff08;类似于 c 语言链表&#xff0c;通过指针指向数据记录的内存地址&#xff09;。 使用索引后可以不用扫描全表来定位某行的数据&#xff0c;而是先通过索…

Redis-运维

转自 极客时间 Redis 亚风 原文视频&#xff1a;https://u.geekbang.org/lesson/535?article681062 Redis 同步 Redis主从数据同步,主从第⼀次同步是全量同步 replicaof 主机 端口 #当前这个机器做Master的备份master如何判断slave是不是第⼀次来同步数据&#xff1a; Repl…

掌握iText:轻松实现固定pdf模板的动态数据填充

推荐语 如果你在工作中需要处理大量的PDF表单&#xff0c;那么使用iText5实现固定PDF模板的动态数据填充&#xff0c;将是一种非常有效的方法。这篇技术文章详细介绍了如何使用iText5库来读取已有的PDF模板&#xff0c;并动态地填充表单数据&#xff0c;生成最终的表单文件。通…

2.苍穹外卖-day02

苍穹外卖-day02 课程内容 新增员工 员工分页查询 启用禁用员工账号 编辑员工 导入分类模块功能代码 功能实现&#xff1a;员工管理、菜品分类管理。 员工管理效果&#xff1a; 菜品分类管理效果&#xff1a; 1. 新增员工 1.1 需求分析和设计 1.1.1 产品原型 一般在做需求分…

基础js逆向练习-登录密码破解(js逆向)

练习平台&#xff1a;逆向账号密码 https://login1.scrape.center/ 直接打开平台&#xff0c;输入密码账号&#xff0c;抓包找到加密的参数携带的位置&#xff0c;这边我们找到的是一个叫token的加密参数&#xff0c;这个参数的携带是一个密文 我们首先考虑一下搜索这个加密的…
最新文章