当前位置: 首页 > java >正文

《数据结构之美--二叉树》

一:引言:

上次我们学习了栈和队列这两个数据结构,今天我们来学习一个新的数据结构–二叉树中的堆。
堆其实就是一种特殊的二叉树,具有二叉树的性质的同时,还具有其他的性质。
那么在学习堆之前还是先来了解一下树。

二:树

1.树的概念与结构

树是⼀种非线性的数据结构,它是由 n(n>=0) 个有限结点组成⼀个具有层次关系的集合。把它叫做
树是因为它看起来像⼀棵倒挂的树,也就是说它是根朝上,而叶朝下的。

在数据结构中树的结构如下图所示:
在这里插入图片描述
树有⼀个特殊的结点,称为根结点,根结点没有前驱结点。
上面这棵树的根节点就是A
除根结点外,其余结点被分成 M(M>0)个互不相交的集合 T1、T2、……、Tm,其中每⼀个集合Ti(1 <= i <= m)又是⼀棵结构与树类似的子树。每棵子树的根结点有且只有⼀个前驱,可以有 0个或多个后继。因此,树是递归定义的。

注:树形结构中,子树之间不能有交集。
例如下面的这些就不是树,而是图
在这里插入图片描述

  1. 子树是不相交的(如果存在相交就是图了)
  2. 除了根结点外,每个结点有且仅有⼀个父结点
  3. ⼀棵N个结点的树有N-1条边

2. 树的相关术语

在这里插入图片描述

  1. 父结点/双亲结点:若⼀个结点含有子结点,则这个结点称为其子结点的父结点;
    如上图:A是B的父结点。
  2. 子结点/孩子结点:⼀个结点含有的子树的根结点称为该结点的子结点;
    如上图:B是A的孩子结点。
  3. 结点的度:⼀个结点有几个孩子,他的度就是多少;
    比如A的度为6,F的度为2,K的度为0。
  4. 树的度:⼀棵树中,最大的结点的度称为树的度;
    如上图:树的度为 6。
  5. 叶子结点/终端结点:度为 0的结点称为叶结点;
    如上图: B、C、H、I…等结点为叶结点。
  6. 分支结点/非终端结点:度不为 0的结点;
    如上图: D、E、F、G…等结点为分支结点。
  7. 兄弟结点:具有相同父结点的结点互称为兄弟结点(亲兄弟);
    如上图: B、C是兄弟结点。
  8. 结点的层次:从根开始定义起,根为第 1层,根的子结点为第 2层,以此类推;
  9. 树的高度或深度:树中结点的最大层次;
    如上图:树的高度为 4。
  10. 结点的祖先:从根到该结点所经分支上的所有结点;
    如上图: A是所有结点的祖先。
  11. 路径:⼀条从树中任意节点出发,沿父节点-子节点连接,达到任意节点的序列;比如A到Q的路径为:A-E-J-Q;H到Q的路径H-D-A-E-J-Q
  12. 子孙:以某结点为根的子树中任⼀结点都称为该结点的子孙。
    如上图:所有结点都是A的子孙。
  13. 森林:由 m(m>0)棵互不相交的树的集合称为森林。

3. 树的表示

孩子兄弟表示法:

的结构相对线性表就比较复杂了,要存储表示起来就比较麻烦了,既然保存值域,也要保存结点和结点之间的关系,实际中有很多种表示方式如:双亲表示法孩子表示法孩子双亲表示法以及孩子兄弟表示法等。我们这里就简单的了解其中最常用的孩子兄弟表示法

struct TreeNode
{
struct Node* child; // 左边开始的第⼀个孩⼦结点
struct Node* brother; // 指向其右边的下⼀个兄弟结点
int data; // 结点中的数据域
};
下面来看一棵树的存储:

这是一颗二叉树
在这里插入图片描述
这是二叉树的节点的物理存储结构:
在这里插入图片描述

4. 树形结构的实际应用场景

文件系统是计算机存储和管理文件的⼀种方式,它利用树形结构来组织和管理文件和文件夹。在文件系统中,树结构被广泛应用,它通过父结点子结点之间的关系来表示不同层级的文件和文件夹之间的关联。
在这里插入图片描述
在这里插入图片描述

三:二叉树

1. 概念与结构

在树形结构中,我们最常用的就是二叉树,⼀棵⼆叉树是结点的一个有限集合,该集合由一个根结点加上两棵别称为左子树和右子树的二叉树组成或者为空。

在这里插入图片描述
从上图可以看出二叉树具备以下特点:

  1. 二叉树不存在度大于 2 的结点。
  2. 二叉树的子树有左右之分,次序不能颠倒,因此二叉树有序树

注:二叉树都是由以下几种情况复合而成的:
在这里插入图片描述

现实中的二叉树
在这里插入图片描述

四:特殊的二叉树

1. 满二叉树

⼀个二叉树,如果每⼀个层的结点数都达到最大值,则这个⼆叉树就是满⼆叉树。也就是说,如果⼀个⼆叉树的层数为 K,且结点总数是2 ^ K − 1,则它就是满⼆叉树
在这里插入图片描述

2. 完全二叉树

完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K的,有 n 个结点的⼆叉树,当且仅当其每⼀个结点都与深度为K的满二叉树中编号从 1至 n 的结点一 一对应时称之为完全二叉树。要注意的是满二叉树是一种特殊的完全二叉树
在这里插入图片描述

3. 二叉树性质

根据满二叉树的特点可知:

  1. 若规定根节点的层数为1,则一颗非空二叉树的第k层上最多有2^(k - 1)个节点。
  2. 若规定根节点的层数为1,则深度为h的二叉树的最大节点数为 2^k - 1
  3. 若规定根节点的层数为1,具有n个节点的满二叉树的深度h = log2(n + 1)

五: 二叉树存储结构

二叉树一般可以使用两种结构存储,一种顺序结构,一种链式结构

1. 顺序结构

顺序结构存储就是使用数组来存储,一般使用数组只适合表示完全二叉树,因为不是完全二叉树会有空间的浪费,完全二叉树更适合使用顺序结构存储。

在这里插入图片描述
在这里插入图片描述
现实中我们通常把(⼀种二叉树)使用顺序结构数组来存储,需要注意的是这里的操作系统虚拟进程地址空间中的堆是两回事,一个是数据结构,一个是操作系统管理内存的一块区域分段

2. 链式结构

二叉树链式存储结构是指,用链表来表示⼀棵二叉树,即用链来指示元素的逻辑关系。通常的方法是链表中每个结点由三个域组成,数据域左右指针域,左右指针分别用来给出该结点左孩子右孩子所在的链结点的存储地址。链式结构又分为二叉链三叉链,当前我们学习中一般都是二叉链。等后面学到高阶数据结构红黑树等会用到三叉链
在这里插入图片描述

在这里插入图片描述

六:实现顺序结构的二叉树:堆

约定:这里我们按照大根堆的逻辑来实现,只要大根堆的逻辑实现了,小根堆只需在一些地方简单修改即可。

⼀般使用顺序结构数组来存储数据,是一种特殊二叉树,具有二叉树的特性的同时,还具备其他的特性。

1. 堆的结构和性质

在这里插入图片描述

小根堆图示:

在这里插入图片描述

大根堆图示:

在这里插入图片描述

堆的性质:
  1. 中某个结点的值总是不大于不小于父结点的值;
  2. 总是一棵完全二叉树

在这里插入图片描述

2. 定义堆结构

在这里插入图片描述

3. 初始化

声明:

在这里插入图片描述

逻辑实现:

在这里插入图片描述

4. 入堆

声明:

在这里插入图片描述

逻辑分析:

入堆很简单,和顺序表的插入数据相同,但难点是在插入数据之后还要维护该为一个大根堆,这里我们的处理方法是:空间足够的情况下直接往最后面插入,接着执行向上调整算法,直到满足大根堆

向上调整算法
  1. 先将元素插入到堆的末尾,即最后⼀个孩子之后。
  2. 插入之后如果的性质遭到破坏,将新插入结点顺着其双亲往上调整到合适位置即可
    下面给出一次向上调整算法执行过程的图示:

在这里向上调整算法维护的是小根堆
在这里插入图片描述

向上调整算法逻辑实现:

由于牵扯到数组元素的交换,所以这里我们封装一个交换函数:
在这里插入图片描述

在这里插入图片描述

向上调整算法时间复杂度分析:

在这里插入图片描述
分析:
第一层,2^0 个节点,需要向上移动0层
第二层,2^1个节点, 需要向上移动1层
第三层,2^3个节点, 需要向上移动2层

第h层,2^(h - 1) 个节点, 需要向上移动h - 1层
则需要移动结点总的移动步数为:每层结点个数*向上调整次数(第⼀层调整次数为0)

在这里插入图片描述
向上调整算法建堆时间复杂度为:O(n∗ log2 n)

逻辑实现:

在这里插入图片描述

测试:

在这里插入图片描述
可以看到大根堆的堆顶元素为最大的78,测试正常。

5. 判空

既然要出堆,那么你的中要有元素才可以,因此这里实现一个判空函数

声明:

在这里插入图片描述

逻辑实现:

在这里插入图片描述

6. 出堆

声明:

在这里插入图片描述

逻辑分析:

出堆是针对堆顶元素进行的操作,但考虑到之前顺序表都是在尾部删除,这里的也是在尾部进行,因此出堆的流程是:先将堆顶元素与堆的最后一个元素交换位置,然后删去最后一个元素,再执行向下调整算法,直到重新成为大根堆

向下调整算法

向下调整算法有⼀个前提:左右子树必须是一个堆,才能调整

向下调整算法

  1. 堆顶元素与堆中最后⼀个元素进行交换。
  2. 删除最后一个元素
  3. 堆顶元素向下调整到满足特性为止。

先将堆顶元素与最后一个元素交换,然后删去最后一个元素。
在这里插入图片描述

之后从堆顶执行向下调整算法
在这里插入图片描述

向下调整算法逻辑实现

:这里因为我们循环结束的条件为孩子节点走到最后,因此这里传参要多一个堆中有效元素的个数。
在这里插入图片描述

在这里插入图片描述

向下调整算法时间复杂度分析:

在这里插入图片描述

分析:
第1层,2 ^ 0 个结点,需要向下移动h-1层
第2层,2 ^ 1个结点,需要向下移动h-2层
第3层,2 ^ 2个结点,需要向下移动h-3层
第4层,2 ^ 3个结点,需要向下移动h-4层
…第h-1层,2 ^ h - 2 个结点,需要向下移动1层
则需要移动结点总的移动步数为:每层结点个数 * 向下调整次数
在这里插入图片描述
在这里插入图片描述
向下调整算法建堆时间复杂度为:O(n)

逻辑实现:

在这里插入图片描述

测试:

在这里插入图片描述
可以看到我们两次删除都是删去了堆顶元素,测试正常。

7. 取出堆顶元素

声明:

在这里插入图片描述

逻辑实现:

在这里插入图片描述

测试:

在这里插入图片描述
可以看到我们每次取堆顶元素时,取出的都是大根堆中最大的元素。

8. 打印函数

由于我们实现的顺序存储的,因此打印其实就是打印顺序表的逻辑。

声明:

在这里插入图片描述

逻辑实现:

在这里插入图片描述

测试:

在这里插入图片描述

8. 销毁

声明:

在这里插入图片描述

逻辑实现:

在这里插入图片描述

测试:

在这里插入图片描述

七:实现链式结构的二叉树

链表来表示一棵二叉树,即用来指示元素的逻辑关系。通常的方法是链表中每个结点由三个域组成,数据域左右指针域,左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址

1. 手搓二叉树:

链式二叉树结构:

在这里插入图片描述

构建二叉树:

在这里插入图片描述

在这里插入图片描述

遍历二叉树:

按照规则,二叉树的遍历有:前序/中序/后序的递归结构遍历:
1)前序遍历(PreorderTraversal 亦称先序遍历):访问根结点的操作发生在遍历其左右子树之前
访问顺序为:根结点、左子树、右子树
2)中序遍历(Inorder Traversal):访问根结点的操作发生在遍历其左右子树之中(间)
访问顺序为:左子树、根结点、右子树
3)后序遍历(Postorder Traversal):访问根结点的操作发生在遍历其左右子树之后
访问顺序为:左子树、右子树、根结点

前序遍历:
声明:

在这里插入图片描述

逻辑实现:

在这里插入图片描述

中序遍历:
声明:

在这里插入图片描述

逻辑实现:

在这里插入图片描述

后序遍历:
声明:

在这里插入图片描述

逻辑实现:

在这里插入图片描述

打印测试:

在这里插入图片描述

递归逻辑图:

在这里插入图片描述

2. 求二叉树节点个数

声明:

在这里插入图片描述

逻辑分析:

在这里插入图片描述

逻辑实现:

在这里插入图片描述

测试:

在这里插入图片描述
经过调用求二叉树节点的函数在打印可以发现没问题。

3. 求二叉树的叶子节点个数

声明:

在这里插入图片描述

逻辑分析:

在这里插入图片描述

逻辑实现:

在这里插入图片描述

测试:

在这里插入图片描述

4. 求二叉树第K层节点个数

声明:

在这里插入图片描述

逻辑分析:

在这里插入图片描述

逻辑实现:

在这里插入图片描述

测试:

在这里插入图片描述

5. 求二叉树深度

声明:

在这里插入图片描述

逻辑分析:

在这里插入图片描述

逻辑实现:

在这里插入图片描述

测试:

在这里插入图片描述

6. 查找二叉树中的值

声明:

在这里插入图片描述

逻辑分析:

在这里插入图片描述

逻辑实现:

在这里插入图片描述

测试:

先来测试一个二叉树中不存在的节点:
在这里插入图片描述
再测试一个二叉树中存在的节点::

8. 层序遍历(广度优先遍历)

除了先序遍历中序遍历后序遍历外,还可以对二叉树进行层序遍历。设二叉树根结点所在层数为1层序遍历就是从所在二叉树根结点出发,首先访问第层的根结点,然后从左到右访问第层上的结点,接着是第层的结点,以此类推,自上而下自左至右逐层访问树的结点的过程就是层序遍历

声明:

在这里插入图片描述

逻辑分析:

在这里插入图片描述

逻辑实现:

注:这里用到了之前实现的队列,需要加入队列的文件,而且这里还需要注意一个点
tree.c文件中需要包含队列的头文件Queue.h
由于队列需要存储二叉树的节点,因此这里的数据类型为结构体指针,这里在重定义时还要加上关键字struct
在这里插入图片描述

在这里插入图片描述

测试:

在这里插入图片描述

9. 判断二叉树是否为完全二叉树

声明:

在这里插入图片描述

逻辑分析:

在这里插入图片描述

在这里插入图片描述

逻辑实现:

在这里插入图片描述
在这里插入图片描述

测试:

这时候二叉树为非完全二叉树
在这里插入图片描述
我们改变一下链接构造一颗完全二叉树
在这里插入图片描述
可以看到测试没问题。

10. 销毁二叉树

:这里为了改变传入的根节点指针,因此传入了根节点指针的地址(二级指针)
这就是省去了在外面手动将root置为空的操作。

声明:

在这里插入图片描述

逻辑分析:

+-

逻辑实现:

在这里插入图片描述

测试:

在这里插入图片描述

总结:

这篇博客主要介绍了,包括关于的一些术语树的表示树在计算机中的应用树的分类树的两种存储结构->顺序存储结构链式存储结构
下次会分享一些二叉树oj题。

http://www.xdnf.cn/news/2601.html

相关文章:

  • PCI/PXI 总线的可编程电阻卡
  • oracle 数据库查询指定用户下每个表占用空间的大小,倒序显示
  • Java垃圾收集器与内存分配策略深度解析
  • 再看 BBR 到 BBRv3 的公平性改进
  • Hadoop 单机模式(Standalone Mode)部署与 WordCount 测试
  • 深入解析 Babylon.js 中的 TransformNode.lookAt 方法
  • AI大模型应用之按照设计稿还原代码
  • 第36课 常用快捷操作——用“鼠标右键”退出当前命令
  • 计算机考研精炼 计网
  • 网络安全实战指南:从安全巡检到权限维持的应急响应与木马查杀全(命令查收表)
  • 基于YOLO的瓷砖缺陷检测系统设计与实现(附数据集+源码)
  • HarmonyOS NEXT 诗词元服务项目开发上架全流程实战(一、项目介绍及实现效果展示)
  • 使用TortoiseGit进行文件比较
  • 【3分钟准备前端面试】Hybrid开发 谷歌浏览器调试安卓app
  • 【优选算法-二分查找】二分查找算法解析:如何通过二段性优化搜索效率
  • 终端下PgSQL与MySQL常用命令
  • Sql刷题日志(day6)
  • 从视频中学习:从Humanoid-X、UH-1的自动打字幕,到首个人形VLA Humanoid-VLA(自监督数据增强且整合第一人称视角)
  • Vue响应式数据详解
  • 微调灾情分析报告生成模型
  • Golang 学习指南
  • 2025 FIC wp
  • 每日定投40刀BTC(15)20250420 - 20250427
  • 基于esp32实现键值对存储读写c程序例程
  • 码蹄集——输入、输出格式题
  • AI核心技术与应用场景的深度解析
  • 【Java二分查找】
  • 脏读、幻读、可重复读
  • 如何查看 MySQL 的 innodb_lock_wait_timeout 值
  • Java EE 计算机的操作系统