C语言_数据结构_二叉树

news2025/3/30 15:33:46

【本节目标】

  • 树的概念及结构

  • 二叉树的概念及结构

  • 二叉树的顺序结构及实现

  • 二叉树的链式结构及实现

1. 树的概念及结构

1.1 树的概念

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

  • 有一个特殊的结点,称为根结点,根节点没有前驱结点
  • 每棵子树的根结点有且只有一个前驱,可以有0个或多个后继
  • 因此,树是递归定义的。

注意:树形结构中,子树之间不能有交集,否则就不是树形结构。

在这里插入图片描述

1.2 树的相关概念

在这里插入图片描述

节点的度:一个节点含有的子树的个数称为该节点的度; 如上图:A的为6。

叶节点或终端节点:度为0的节点称为叶节点; 如上图:B、C、H、I…等节点为叶节点。

非终端节点或分支节点:度不为0的节点; 如上图:D、E、F、G…等节点为分支节点。

双亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点; 如上图:A是B的父节点。

孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点; 如上图:B是A的孩子节点。

兄弟节点:具有相同父节点的节点互称为兄弟节点; 如上图:B、C是兄弟节点。

树的度:一棵树中,最大的节点的度称为树的度; 如上图:树的度为6。

节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推。

树的高度或深度:树中节点的最大层次; 如上图:树的高度为4。

堂兄弟节点:双亲在同一层的节点互为堂兄弟;如上图:H、I互为兄弟节点。

节点的祖先:从根到该节点所经分支上的所有节点;如上图:A是所有节点的祖先。

子孙:以某节点为根的子树中任一节点都称为该节点的子孙。如上图:所有节点都是A的子孙。

森林:由m(m>0)棵互不相交的树的集合称为森林。

1.3 树的表示

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

typedef int DataType;
struct Node
{
	struct Node* _firstChild1;    
	struct Node* _pNextBrother;   
	DataType _data;               
};

画图详解:

在这里插入图片描述

2. 二叉树的概念及结构

在这里插入图片描述

2.1 概念

一棵二叉树是结点的一个有限集合,该集合:

  1. 或者为空
  2. 由一个根节点加上两棵别称为左子树和右子树的二叉树组成
    在这里插入图片描述
    从上图可以看出:
  • 二叉树不存在度大于2的结点
  • 二叉树的子树有左右之分,次序不能颠倒,因此二叉树是有序树

注意:对于任意的二叉树都是由以下几种情况复合而成的:

在这里插入图片描述

2.2 特殊二叉树

1. 满二叉树:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为K,且结点总数是2*k-1,则它就是满二叉树。
2. 完全二叉树:完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。

在这里插入图片描述

注意:完全二叉树前面都要保证是满的,最后一层节点可以不满,但是必须要保证从左到右的节点是连续的。

2.3 二叉树的存储结构

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

1. 顺序结构

顺序结构存储就是使用数组来存储,一般使用数组只适合表示完全二叉树,因为不是完全二叉树会有空间的浪费。而现实中使用中只有堆才会使用数组来存储,关于堆我们后面的章节会专门讲解。二叉树顺序存储在物理上是一个数组,在逻辑上是一颗二叉树。

在这里插入图片描述

在这里插入图片描述

2. 链式结构

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

如下代码:

typedef int BTDataType;

 // 二叉链
struct BinaryTreeNode
{
  	struct BinTreeNode* _pLeft;   
	// 指向当前节点左孩子
	struct BinTreeNode* _pRight; // 指向当前节点右孩子
	BTDataType _data; // 当前节点值域
}

// 三叉链
struct BinaryTreeNode
{
	struct BinTreeNode* _pParent; // 指向当前节点的双亲
	struct BinTreeNode* _pLeft;   
	// 指向当前节点左孩子
	struct BinTreeNode* _pRight; // 指向当前节点右孩子
	BTDataType _data; // 当前节点值域
}

3. 二叉树的顺序结构及实现

3.1 二叉树的顺序结构

普通的二叉树是不适合用数组来存储的,因为可能会存在大量的空间浪费。而完全二叉树更适合使用顺序结构存储。现实中我们通常把堆(一种二叉树)使用顺序结构的数组来存储,需要注意的是这里的堆和操作系统虚拟进程地址空间中的堆是两回事,一个是数据结构,一个是操作系统中管理内存的一块区域分段。

在这里插入图片描述

3.2 堆的概念及结构

将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。

  • 堆中某个节点的值总是不大于或不小于其父节点的值。
  • 堆总是一棵完全二叉树。

在这里插入图片描述

3.2.1 前置准备

在这里插入图片描述

3.2.2 初始化

在这里插入图片描述

3.2.3 销毁

在这里插入图片描述

3.2.4 入堆

在这里插入图片描述

3.2.5 出堆

在这里插入图片描述

3.2.6 返回堆内元素个数

在这里插入图片描述

3.2.7 返回堆头元素

在这里插入图片描述

3.2.8 代码演示:

在这里插入图片描述

输出结果:

在这里插入图片描述

3.2.9 堆总结

再学习堆的过程中,物理结构是数组形式,但是在逻辑层面,我们把他想象成完全二叉树或者满二叉树。

  1. 大堆:父节点要大于或者等于子节点
  2. 小堆:父节点要小于或者等于子节点

最核心的就是入堆的向上调整以及堆顶释放的向下调整

  1. 向上调整:当我们入堆的时候,需要考虑到堆的特性,所以我们需要不断的跟父节点进行调整,直至满足条件就不再调整。

  2. 向下调整:当我们堆顶释放的时候,在尽可能不动其他节点的情况下,进行首尾交换,然后删除堆尾,这个时候就要把堆顶的这个交换上来的元素再次往下进行不断的调整,直至满足条件就不再调整。

3.3 堆排序

3.3.1 方法一:

思路:将数组的元素依次放入堆结构进行入堆操作,然后再次通过出堆拷贝回数组。
弊端:

  1. 需要先造轮子,写出堆的各个功能点。
  2. 额外产生空间 + 拷贝

代码演示:

在这里插入图片描述

输出结果:

在这里插入图片描述

3.3.2 方法二

思路:将数组看作堆,从第二个元素开始不断进行向上调整,直至最后一个元素,即可实现堆结构

这其实和入堆操作相似,只不过我们本身就是数组,需要内部从第二个元素下标开始向上调整(此操作要遍历到最后一个元素都要进行向上调整),入堆是进一个则进行一次向上调整。

代码演示:

在这里插入图片描述

思路延续:

以上我们即可实现堆结构,但是离堆排序还是差了一点,再不考虑扩充空间以及拷贝的情况下,我们还需要思考从数组本身进行调整。
如果我们要实现的是升序数组,那么我们不能一开始就调成小堆,这样会难以从自身进行堆排序成升序情况,而是一开始要从反方向大堆开始。

以下是进行堆排序的方法:
由上文接着往下探讨,因为我们是反方向进行调整成大堆,所以我们很清楚的知道堆顶元素是整个数组最大的元素。
首尾进行交换,此时我们就把最大的元素放在了数组末尾,那么我们需要重新进行向下调整重新调整堆结构。

重点:当我们进行交换以后,我们的边界值是发生了改变的,最开始的边界值是末尾下标的下一个,(n也就是边界值),但是当我们将最大元素放在了数组末尾以后,也就说明这里的末尾下标是不能发生改变的,所以我们边界值需要变成末尾下标。(此时变成n-1)

最后,我们结束调整的条件是边界值>1,也就是说如果只剩下最后一个元素,也就不需要进行向下调整。

代码演示:

在这里插入图片描述

代码演示:

在这里插入图片描述

输出结果:

在这里插入图片描述

3.3.3 拓展_向下调整建堆:

在这里插入图片描述

叙述:再除终端节点以外的子节点开始作为父节点进行向下调整,最终实现堆结构。

4. 二叉树链式结构的实现

4.1 前置准备

在学习二叉树的基本操作前,需先要创建一棵二叉树,然后才能学习其相关的基本操作。由于现在大家对二叉树结构掌握还不够深入,为了降低大家学习成本,此处手动快速创建一棵简单的二叉树,快速进入二叉树操作学习,等二叉树结构了解的差不多时,我们反过头再来研究二叉树真正的创建方式。

在这里插入图片描述

注意:上述代码并不是创建二叉树的方式,真正创建二叉树方式后序详解重点讲解。

再看二叉树基本操作前,再回顾下二叉树的概念,二叉树是:

  1. 空树
  2. 非空:根节点,根节点的左子树、根节点的右子树组成的。

4.2 二叉树的遍历

4.2.1 前、中、后序遍历

学习二叉树结构,最简单的方式就是遍历。所谓二叉树遍历(Traversal)是按照某种特定的规则,依次对二叉树中的节点进行相应的操作,并且每个节点只操作一次。访问结点所做的操作依赖于具体的应用问题。 遍历是二叉树上最重要的运算之一,也是二叉树上进行其它运算的基础。

// 二叉树前序遍历 
void PreOrder(BTNode* root);
 // 二叉树中序遍历
void InOrder(BTNode* root);
 // 二叉树后序遍历
void PostOrder(BTNode* root);

在这里插入图片描述

4.2.2 前序遍历画图详解

代码演示:

在这里插入图片描述

在这里插入图片描述

4.2.3 中序遍历画图详解

代码演示:

在这里插入图片描述

在这里插入图片描述

4.2.4 后续遍历画图详解

代码演示:

在这里插入图片描述

在这里插入图片描述

4.2.5 层序遍历

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

画图详解:

在这里插入图片描述

层序遍历最好的办法是通过队列形式进行遍历。

讲解:二叉树通过祖先节点往下遍历可以访问到各个节点的值,首先创建一个队列,实现各个功能点。将二叉树的祖先节点作为值进行入队操作,然后拿到队头元素,进行出队操作,但这里有个核心步骤就是,当根节点进行出队操作的时候,需要把他下面关联的左右子节点进行入队操作。还需要判断左右子节点是否为空,为空的就自然不进行入队操作。

重点:如果二叉树节点作为队头元素进行出队操作,再出队以后需要将该节点的左右子节点依次在队尾进行入队操作。

画图详解:

在这里插入图片描述

代码演示:

在这里插入图片描述

输出结果:

在这里插入图片描述

简单递归练习:

在这里插入图片描述

4.3 节点个数以及高度等

4.3.1 二叉树节点个数

方法一:

思路:通过定义全局变量形式,将每个节点都记录下来,不会随着栈帧的销毁而销毁。

在这里插入图片描述

方法二:

在这里插入图片描述

4.3.2 求叶子节点个数

在这里插入图片描述

4.3.3 求二叉树高度

在这里插入图片描述

4.3.4 求第K层的节点数量

在这里插入图片描述

4.3.5 找到二叉树中值为X的节点,并返回地址

在这里插入图片描述

总结:

该函数运用递归的方法,先对当前节点进行检查,若不是目标节点,就先递归查找左子树,要是左子树中未找到,再递归查找右子树。一旦找到目标节点就马上返回该节点的指针,要是遍历完整个二叉树都没找到,就返回 NULL。

4.4 二叉树基础OJ题练习

4.4.1 检查两颗树的节点值是否全部相同

在这里插入图片描述

代码演示:

在这里插入图片描述

输出结果:

在这里插入图片描述

4.4.2 单值二叉树

在这里插入图片描述

代码演示:

在这里插入图片描述

输出结果:

在这里插入图片描述

总结:

在本题中我们需要注意的是由根节点判断和下面的左右子节点是否相同。

  1. 左右子节点如果存在,且值不同,则说明不是单值二叉树,可以返回false。
  2. 左子节点或者右子节点不存在,则说明根节点是叶子节点,说明此时为叶子节点
  3. 当根节点为NULL的时候,说明左或者右半部分为单值节点返回true

isValueBTree 函数用于判断二叉树所有节点值是否相同。若为空树返回 true;若当前节点的左或右子节点存在且值与当前节点不同,返回 false;否则递归检查左右子树,只有二者都满足节点值相同才返回 true。时间复杂度 O(n),最坏空间复杂度 O(n)。

4.4.3 对称二叉树

在这里插入图片描述

代码演示:

在这里插入图片描述

输出结果:

在这里插入图片描述

4.4.4 二叉树前序遍历

在这里插入图片描述

代码演示:

在这里插入图片描述

输出结果:

在这里插入图片描述

4.4.5 另一颗树的子树

在这里插入图片描述

代码演示:

在这里插入图片描述

输出结果:

在这里插入图片描述

4.4.6 求出左叶子节点的和

在这里插入图片描述

代码演示:

在这里插入图片描述

总结:

GetleftNode 函数用于计算二叉树中所有左叶子节点值的总和。若根节点为空,返回 0;若当前节点的左子节点是叶子节点,记录其值;递归计算左右子树中左叶子节点值的总和并累加返回。

4.5 二叉树的创建与销毁

4.5.1 通过前序遍历的数组“a b c # # d e # g # # f # # #”进行二叉树的创建

代码演示:

在这里插入图片描述

输出结果:

在这里插入图片描述

总结:本来都是简单的创建节点,但是在返回节点的过程中完成了节点与节点的连接操作。

4.5.2 二叉树的销毁

在这里插入图片描述

4.5.3 判断是否是完全二叉树

在这里插入图片描述

4.6 课后选择题

  1. 某完全二叉树按层次输出(同一层从左到右)的序列为 ABCDEFGH 。该完全二叉树的前序序列为( )

A.ABDHECFG
B.ABCDEFGH
C.HDBEAFCG
D.HDEBFGCA

画图详解:

在这里插入图片描述

  1. 二叉树的先序遍历和中序遍历如下:先序遍历:EFHIGJK;中序遍历:HFIEJKG.则二叉树根结点为()

A.E
B.F
C.G
D.H

画图详解:

在这里插入图片描述

  1. 设一课二叉树的中序遍历序列:badce,后序遍历序列:bdeca,则二叉树前序遍历序列为____。

A.adbce
B.decab
C.debac
D.abcde

画图详解:

在这里插入图片描述

  1. 某二叉树的后序遍历序列与中序遍历序列相同,均为 ABCDEF ,则按层次输出(同一层从左到右)的序列为()

A FEDCBA
B CBAFED
C DEFCBA
D ABCDEF

画图详解:

在这里插入图片描述

选择题答案:

A A D A

本章总结

对于二叉树的学习,更多的是在理解递归的过程,需要自己手动的去画出递归过程中代码是如何进行往前进入以及往后回退的,通过本章的学习能对递归的概念有了更深层次的理解。

本章完~

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

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

相关文章

Compare全目录文件比较内容(项目中用到过)

第一步:找到“会话”——“会话设置” 会话设置弹框信息 第二步:选择“比较”tab标签 比较内容:选中二进制比较 第三步:选中所有文件 第四步:右键选中“比较内容” 第五步:选中“基于规则的比较”

3.26[a]paracompute homework

5555 负载不平衡指多个线程的计算量差异显著,导致部分线程空转或等待,降低并行效率。其核心矛盾在于任务划分的静态性与计算动态性不匹配,尤其在处理不规则数据或动态任务时尤为突出。以稀疏矩阵的向量乘法为例,假设其非零元素分…

视觉大模型CLIP论文精读

论文:Learning Transferable Visual Models From Natural Language Supervision 代码:https://github.com/openai/CLIP 摘要 最先进的计算机视觉系统是针对预测一组固定的、预先确定的对象类别进行训练的。这种受限的监督形式限制了它们的通用性和可用…

链表的创建:头插法与尾插法详解(数据结构)

C 链表的创建:头插法与尾插法详解 链表(Linked List)是一种重要的数据结构,适用于插入和删除操作频繁的场景。本文介绍 两种常见的链表构建方法: 尾插法(Append / Tail Insertion):…

深入解析 Java 类加载机制及双亲委派模型

🔍 Java的类加载机制是确保应用程序正确运行的基础,特别是双亲委派模型,它通过父类加载器逐层加载类,避免冲突和重复加载。但在某些特殊场景下,破坏双亲委派模型会带来意想不到的效果。本文将深入解析Java类加载机制、…

MySQL数据库精研之旅第四期:解锁库操作高阶技能

专栏:MySQL数据库成长记 个人主页:手握风云 目录 一、查看所有表 1.1. 语法 二、创建表 2.1. 语法 2.2. 示例 2.3. 表在磁盘上对应的⽂件 三、查看表结构 3.1. 语法 3.2. 示例 四、修改表 4.1. 语法 4.2. 示例 五、删除表 5.1. 语法 5.2.…

【DevOps】DevOps and CI/CD Pipelines

DevOps 是一种将开发与运维实践相结合的模式,旨在缩短软件开发周期并交付高质量软件。 DevOps 是什么? 开发团队与运维团队之间的协作 • 持续集成与持续交付(CI/CD) • 流程自动化 • 基础设施即代码(IaC)…

VS自定义静态库并在其他项目中使用

1、VS创建一个空项目或者静态库项目 2、右键项目 属性 修改生成文件类型 3、生成解决方案 4、复制.h文件和.lib文件作为静态库 5、创建一个新项目 测试使用新生成的静态库 在新项目UseStaticLib中加一个新文件夹lib,lib中放入上面的.h和.lib文件。 6、vs中右…

力扣32.最长有效括号(栈)

32. 最长有效括号 - 力扣&#xff08;LeetCode&#xff09; 代码区&#xff1a; #include<stack> #include<string> /*最长有效*/ class Solution { public:int longestValidParentheses(string s) {stack<int> st;int ans0;int ns.length();st.push(-1);fo…

vue3 项目中预览 word(.docx)文档方法

vue3 项目中预览 word&#xff08;.docx&#xff09;文档方法 通过 vue-office/docx 插件预览 docx 文档通过 vue-office/excel 插件预览 excel 文档通过 vue-office/pdf 插件预览 pdf 文档 安装插件 npm install vue-office/docx vue-demi示例代码 <template><Vu…

DHCP(Dynamic Host Configuration Protocol)原理深度解析

目录 一、DHCP 核心功能 二、DHCP 工作流程&#xff08;四阶段&#xff09; 三、关键技术机制 1. 中继代理&#xff08;Relay Agent&#xff09; 2. Option 82&#xff08;中继信息选项&#xff09; 3. 租期管理 4. 冲突检测 四、DHCP 与网络架构交互 1. MLAG 环境 2.…

创建login.api.js步骤和方法

依次创建 login.api.js、home.api.js...... login.api.js、home.api.js 差不多 导入到 main.js main.js 项目中使用

基于springboot二手交易平台(源码+lw+部署文档+讲解),源码可白嫖!

摘要 人类现已迈入二十一世纪&#xff0c;科学技术日新月异&#xff0c;经济、资讯等各方面都有了非常大的进步&#xff0c;尤其是资讯与网络技术的飞速发展&#xff0c;对政治、经济、军事、文化等各方面都有了极大的影响。 利用电脑网络的这些便利&#xff0c;发展一套二手交…

帕金森患者的生活重塑:从 “嘴” 开启康复之旅

当提到帕金森病&#xff0c;许多人会联想到震颤、僵硬和行动迟缓等症状。这种神经系统退行性疾病&#xff0c;给患者的生活带来了巨大的挑战。然而&#xff0c;你可知道&#xff0c;帕金森患者恢复正常生活&#xff0c;可以从 “嘴” 开始管理&#xff1f; 帕金森病在全球影响着…

JVM 为什么不使用引用计数算法?——深入解析 GC 策略

在 Java 中&#xff0c;垃圾回收&#xff08;Garbage Collection, GC&#xff09;是一个至关重要的功能&#xff0c;它能够自动管理内存&#xff0c;回收不再使用的对象&#xff0c;从而防止内存泄漏。然而&#xff0c;在垃圾回收的实现上&#xff0c;JVM 并未采用引用计数算法…

【HarmonyOS NEXT】EventHub和Emitter的使用场景与区别

一、EventHub是什么&#xff1f; 移动应用开发的同学应该比较了解EventHub&#xff0c;类似于EventBus。标准的事件广播通知&#xff0c;订阅&#xff0c;取消订阅的处理。EventHub模块提供了事件中心&#xff0c;提供订阅、取消订阅、触发事件的能力。 类似的框架工具有很多…

01-系统编程

一、程序和进程的区别&#xff1a; window系统&#xff1a; 1、程序存储在硬盘中&#xff0c;文件格式为.exe后缀&#xff0c;静态的 2、进程运行在内存中&#xff0c;动态的 Linux系统 1、程序存储在硬盘中&#xff0c;文件格式为.ELF&#xff08;可执行的链接文件&#…

Linux编译器gcc/g++使用完全指南:从编译原理到动静态链接

一、gcc/g基础认知 在Linux开发环境中&#xff0c;gcc和g是我们最常用的编译器工具&#xff1a; gcc&#xff1a;GNU C Compiler&#xff0c;专门用于编译C语言程序g&#xff1a;GNU C Compiler&#xff0c;用于编译C程序&#xff08;也可编译C语言&#xff09; &#x1f4cc…

26考研|数学分析:定积分及应用

这一部分作为数学分析的灵魂&#xff0c;在数学分析的计算中&#xff0c;绝大部分的问题都可以转换成定积分的计算问题&#xff0c;所以在这部分的学习中&#xff0c;一定要注意提升计算能力&#xff0c;除此之外&#xff0c;由积分引出的相关积分不等式也是分析的重点和难点&a…

扩展卡尔曼滤波

1.非线性系统的线性化 标准卡尔曼滤波 适用于线性化系统&#xff0c;扩展卡尔曼滤波 则扩展到了非线性系统&#xff0c;核心原理就是将非线性系统线性化&#xff0c;主要用的的知识点是 泰勒展开&#xff08;我另外一篇文章的链接&#xff09;&#xff0c;如下是泰勒展开的公式…