数据结构:详解【树和二叉树】
1. 树的概念及结构(了解)
1.1 树的概念
树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。
1.2 树的结构

1.3 树与非树:

1.4 树在实际中的运用(表示文件系统的目录树结构)

2. 与树的结构相关的概念


- 节点的度:一个节点含有的子树的个数称为该节点的度; 如上图:A的为6。
- 叶节点或终端节点:度为0的节点称为叶节点; 如上图:B、C、H、I…等节点为叶节点。
- 非终端节点或分支节点:度不为0的节点; 如上图:D、E、F、G…等节点为分支节点。
- 双亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点; 如上图:A是B的父节点。
- 孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点; 如上图:B是A的孩子节点。
- 兄弟节点:具有相同父节点的节点互称为兄弟节点; 如上图:B、C是兄弟节点。
- 树的度:一棵树中,最大的节点的度称为树的度; 如上图:树的度为6。
- 节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推;
- 树的高度或深度:树中节点的最大层次; 如上图:树的高度为4。
- 森林:由m(m>0)棵互不相交的多颗树的集合称为森林;(数据结构中的学习并查集本质就是一个森林)。
3. 二叉树的概念及结构
2.1 概念
一棵二叉树是结点的一个有限集合,该集合或者为空,或者是由一个根节点加上两棵别称为左子树和右子树的二叉树组成。
2.2 二叉树的特点:
1.每个结点最多有两棵子树,即二叉树不存在度大于2的结点。
2.二叉树的子树有左右之分,其子树的次序不能颠倒。
2.3 两种特殊的二叉树
(1)满二叉树:
一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为K,且结点总数是2k -1 ,则它就是满二叉树。
(2)完全二叉树:
对于深度为K的,有n个结点的二叉树,如果满足前K-1层都是满的,最后一层不满,但最后一层从左到右都是连续的。则这个二叉树就是完全二叉树。

(3)对这两种二叉树的有关数据的推导

4. 二叉树的性质
- 若规定根节点的层数为1,则一棵非空二叉树的第i层上最多有2^(i-1)个结点。
- 若规定根节点的层数为1,则深度为h的二叉树的最大结点数是2^h- 1。
- 对任何一棵二叉树, 如果度为0其叶结点个数为 n0, 度为2的分支结点个数为 n2,则有n0=n2+1。
- 若规定根节点的层数为1,具有n个结点的满二叉树的深度,h = logN(以2为底)。
5. 二叉树的存储
5.1 顺序存储 :
顺序结构存储就是使用数组来存储,一般使用数组只适合表示完全二叉树,因为不是完全二叉树会有空间的浪费。而现实中使用中只有堆才会使用数组来存储,关于堆我们后面的章节会专门讲解。二叉树顺序存储在物理上是一个数组,在逻辑上是一颗二叉树。

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

6. 二叉树的前,中,后序遍历
要实现前,中,后序遍历,我们需要再来理解二叉树的结构。把任一一棵二叉树分为三部分:根节点,左子树,右子树。
我们这里先拿一棵简单的二叉树举例:

6.1 二叉树的前序(先根)遍历:
根,左子树,右子树。
对应上图为:A B D NULL NULL E NULL NULL C NULL NULL。
6.2 二叉树的中序(中根)遍历:
左子树,根,右子树。
对应上图为:NULL D NULL B NULL E NULL A NULL C NULL。
6.3 二叉树的后序(后根)遍历:
左子树,右子树,根。
对应上图为:NULL NULL D NULL NULL E B NULL NULL C A。
7. 有关二叉树的常用功能的实现
7.1 三序(深度优先)遍历的代码实现
这里我们需要用到分治算法: 分而治之,把大问题分成类似的子问题,子问题再分成子问题……直到子问题不可再分割。实际上就是递归思想。
7.2 根据上图代码实现如下:
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h>
#include <stdlib.h>typedef char BTDataType;//定义二叉树的结构
typedef struct BinaryTreeNode
{BTDataType data;//存放的数据struct BinaryTreeNode* left;//左子树struct BinaryTreeNode* right;//右子树
}BTNode;//前序遍历
void PrevOrder(BTNode* root)//根节点
{if (root == NULL){printf("NULL ");return;}printf("%c ", root->data);PrevOrder(root->left);PrevOrder(root->right);
}//中序遍历
void InOrder(BTNode* root)//根节点
{if (root == NULL){printf("NULL ");return;}InOrder(root->left);printf("%c ", root->data);InOrder(root->right);}//后序遍历
void PostOrder(BTNode* root)//根节点
{if (root == NULL){printf("NULL ");return;}PostOrder(root->left);PostOrder(root->right);printf("%c ", root->data);}void TreeTest()
{//1.开辟节点和初始化BTNode* A = (BTNode*)malloc(sizeof(BTDataType));if (A == NULL){perror("malloc fail\n");return;}A->data = 'A';A->left = NULL;A->right = NULL;BTNode* B = (BTNode*)malloc(sizeof(BTDataType));if (B == NULL){perror("malloc fail\n");return;}B->data = 'B';B->left = NULL;B->right = NULL;BTNode* C = (BTNode*)malloc(sizeof(BTDataType));if (C == NULL){perror("malloc fail\n");return;}C->data = 'C';C->left = NULL;C->right = NULL;BTNode* D = (BTNode*)malloc(sizeof(BTDataType));if (D == NULL){perror("malloc fail\n");return;}D->data = 'D';D->left = NULL;D->right = NULL;BTNode* E = (BTNode*)malloc(sizeof(BTDataType));if (E == NULL){perror("malloc fail\n");return;}E->data = 'E';E->left = NULL;E->right = NULL;//2.链接各个节点A->left = B;A->right = C;B->left = D;B->right = E;//3.进行输出PrevOrder(A);printf("\n");InOrder(A);printf("\n");PostOrder(A);printf("\n");
}int main()
{TreeTest();return 0;
}
输出结果与我们分析的相同:

7.22 前序函数递归展开图:

中序和后续的递归展开图类似,读者自行分析。
7.3 计算一棵二叉树的总节点数
方法 1:遍历递归计数,定义局部变量size,传地址计数
代码实现如下:
void TreeSize(BTNode* root,int *psize)
{if (root == NULL){return;}else{(*psize)++;}TreeSize(root->left, psize);TreeSize(root->right, psize);}void TreeTest()
{......//续上上文的代码和图int Asize = 0;TreeSize(A, &Asize);printf("Asize:%d\n", Asize);int Bsize = 0;TreeSize(B, &Bsize);printf("Bsize:%d\n", Bsize);}
方法2:分治思想,递归
代码实现如下:
int TreeSize(BTNode* root)
{return root == NULL ? 0 : TreeSize(root->left) + TreeSize(root->right) + 1;
}void TreeTest()
{......//续上上文的代码和图printf("Asize:%d\n",TreeSize(A) );printf("Bsize:%d\n",TreeSize(B) );}
递归调用可抽象为:

两种方法的输出结果均是:

7.4 计算一棵二叉树中叶子节点的个数
利用分治思想,后序遍历。
代码实现如下:
int TreeLeafSize(BTNode* root)
{if (root == NULL)return 0;//是叶节点if (root->left == NULL && root->right == NULL)return 1;//左边的叶节点+右边的叶节点return TreeLeafSize(root->left) + TreeLeafSize(root->right);
}void TreeTest()
{......//续上上文的代码和图printf("LeafSize:%d\n",TreeLeafSize(A) );
}
输出结果是:

7.5 计算二叉树的最大深度
利用分治思想,后序遍历,当根节点为NULL时,返回0;当根节点不为NULL时,分解子问题,先求左右子树的深度,该节点的深度 = 左右子树更大的那一个+1。
代码实现如下:
int MaxDepth(BTNode* root)
{if (root == NULL)return 0 ;int leftdepth = MaxDepth(root->left);int rightdepth = MaxDepth(root->right);return leftdepth > rightdepth ? leftdepth + 1 : rightdepth + 1;}void TreeTest()
{......//续上上文的代码和图printf("MaxDepth:%d\n",MaxDepth(A) );
}
输出结果是:

7.6 销毁二叉树
销毁二叉树不能从根节点开始销毁,不然会找不到其他节点。要用后序遍历,先左节点,右节点,最后根节点。
代码实现如下:
void DestoryTree(BTNode* root)
{if (root == NULL)return;DestoryTree(root->left);DestoryTree(root->right);free(root);root = NULL;
}
8. 二叉树的层序(广度优先)遍历
8.1 什么是层序遍历
设二叉树的根节点所在层数为1,层序遍历就是从所在二叉树的根节点出发,首先访问第一层的树根节点,然后从左到右访问第2层上的节点,接着是第三层的节点,以此类推,自上而下,自左至右逐层访问树的结点的过程就是层序遍历。

8.2 层序遍历的代码实现
要实现二叉树的层序遍历,我们需要借助队列的先进先出的特性。其核心思想是:上一层的一个节点出的时候带其下一层的子节点进。
画图解释如下:

代码实现如下:
void LealOrder(BTNode* root)
{Queue q;QueueInit(&q);if (root)QueuePush(&q, root);while (!QueueEmpty(&q)){BTNode* front = QueueFront(&q);//取出队头QueuePop(&q);printf("%c ", front->data);if (front->left)//左不为空,入左节点QueuePush(&q, front->left);if (front->right)//右不为空,入右节点QueuePush(&q, front->right);}printf("\n");QueueDestory(&q);
}void TreeTest()
{......//续上上文的代码和图LealOrder(A);
}
输出结果为:

相关文章:
数据结构:详解【树和二叉树】
1. 树的概念及结构(了解) 1.1 树的概念 树是一种非线性的数据结构,它是由n(n>0)个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝…...
“成像光谱遥感技术中的AI革命:ChatGPT在遥感领域中的应用“
遥感技术主要通过卫星和飞机从远处观察和测量我们的环境,是理解和监测地球物理、化学和生物系统的基石。ChatGPT是由OpenAI开发的最先进的语言模型,在理解和生成人类语言方面表现出了非凡的能力。本文重点介绍ChatGPT在遥感中的应用,人工智能…...
semhear环境sox
这里写自定义目录标题 pip list 看到当前环境下已经有sox了怀疑跟torchaudio和torchvision有关,更新了一下:装了torchvisionsox还是找不到 pip list 看到当前环境下已经有sox了 怀疑跟torchaudio和torchvision有关,更新了一下: p…...
如何快速开启一个项目-ApiHug - API design Copilot
ApiHug101-001开启篇 🤗 ApiHug {Postman|Swagger|Api...} 快↑ 准√ 省↓ GitHub - apihug/apihug.com: All abou the Apihug apihug.com: 有爱,有温度,有质量,有信任ApiHug - API design Copilot - IntelliJ IDEs Plugin |…...
从用友U9到钉钉通过接口配置打通数据
从用友U9到钉钉通过接口配置打通数据 接通系统:用友U9 用友U9cloud深耕制造领域十三载,U9cloud在机械、电子、汽配、家具、整车、军工等细分行业有着深厚的积累,尤其是机械、电子和汽配行业,不但打造了多个成熟的产品模式和应用场…...
PyQt qrc2py 使用PowerShell将qrc文件转为py文件并且将导入模块PyQt或PySide转换为qtpy模块开箱即用
前言 由于需要使用不同的qt环境(PySide,PyQt)所以写了这个脚本,使用找到的随便一个rcc命令去转换qrc文件,然后将导入模块换成qtpy这个通用库(支持pyside2-6,pyqt5-6),老版本的是Qt.py(支持pysi…...
phpstorm设置头部注释和自定义注释内容
先说设置位置: PhpStorm中文件、类、函数等注释的设置在:setting-》Editor-》FIle and Code Template-》Includes-》PHP Function Doc Comment下设置即可,其中方法的默认是这样的: /** ${PARAM_DOC} #if (${TYPE_HINT} ! "…...
【数据分析面试】10. 计算平均通勤时间(SQL:timestampdiff() 和datediff()区别)
题目 假设你在Uber工作。rides表包含了关于Uber用户在美国各地的行程信息。 编写一个查询,以获取纽约(NY)每位通勤者的平均通勤时间(以分钟为单位),以及纽约所有通勤者的平均通勤时间(以分钟为…...
2024年150道高频Java面试题(二十二)
43. ArrayList 和 Vector 的区别是什么? ArrayList 和 Vector 是 Java 中用于存储对象的两种不同类型的动态数组。它们都实现了 List 接口,但存在一些重要的区别: 同步性: ArrayList 是不同步的,意味着它不是线程安全…...
如何使用校园网——Win10笔记本,台式机互开热点
当我们使用校园网的时候,往往只能连接一个电脑端,但是又想两个机子同时连接WIFI怎么办呢? 当然,前提条件是你先得其中一台电脑有网络哈 1、打开想开共享热点的电脑的设置 A、点击WIN,再点击设置 2、点击网络和Inte…...
c#:简洁实现if-else语句
c#:简洁实现if-else语句 在C#中,可以使用三元运算符(? :)来简洁地实现if-else语句。其语法格式为: 条件表达式 ? 表达式1 : 表达式2 例如:当条件表达式为真时,返回表达式1的值,否…...
金融贷款批准预测项目
注意:本文引用自专业人工智能社区Venus AI 更多AI知识请参考原站 ([www.aideeplearning.cn]) 在金融服务行业,贷款审批是一项关键任务,它不仅关系到资金的安全,还直接影响到金融机构的运营效率和风险管理…...
FR中隐藏系统管理--用户管理中 表格中每条数据中的编辑按钮,删除按钮
比如隐藏删除按钮: var userTableTools BI.Constants.getConstant("dec.constant.user.table.tools")for(var key in userTableTools){if(key "delete"){var deleteItem userTableTools["delete"]deleteItem.invisible true;}}...
函数重载和引用【C++】
文章目录 函数重载什么是函数重载?函数重载的作用使用函数重载的注意点为什么C可以函数重载,C语言不行? 引用什么是引用?引用的语法引用的特点引用的使用场景引用的底层实现传参时传引用和传值的效率引用和指针的区别 函数重载 什…...
rust-tokio发布考古
源头: Carl Lerche Aug 4, 2016 I’m very excited to announce a project that has been a long time in the making. 我很兴奋地宣布一个酝酿已久的项目。 Tokio is a network application framework for rapid development and highly scalable deployments…...
3D医疗图像配准 | 基于Vision-Transformer+Pytorch实现的3D医疗图像配准算法
项目应用场景 面向医疗图像配准场景,项目采用 Pytorch ViT 来实现,形态为 3D 医疗图像的配准。 项目效果 项目细节 > 具体参见项目 README.md (1) 模型架构 (2) Vision Transformer 架构 (3) 量化结果分析 项目获取 https://download.csdn.net/down…...
设计模式(18):状态模式
核心 用于解决系统中复杂对象的状态转换以及不同状态下行为的封装问题 结构 环境类(Context): 环境类中维护一个State对象,它定义了当前的状态,并委托当前状态处理一些请求; 抽象状态类(State): 用于封装对象的一个特定状态所对应的行为&a…...
如果用大模型考公,kimi、通义千问谁能考高分?
都说大模型要超越人类了,今天就试试让kimi和通义千问做公务员考试题目,谁能考高分? 测评结果再次让人震惊! 问题提干:大小两种规格的盒装鸡蛋,大盒装23个,小盒装16个,采购员小王买了…...
如何在Java中创建对象输入流
在Java中创建对象输入流(ObjectInputStream)通常涉及以下步骤: 获取源输入流:首先,你需要有一个源输入流,它可能来自文件、网络连接或其他任何可以提供字节序列的源。 包装源输入流:接着&#…...
Vue 打包或运行时报错Error: error:0308010C
问题描述: 报错:Error: error:0308010C 报错原因: 主要是因为 nodeJs V17 版本发布了 OpenSSL3.0 对算法和秘钥大小增加了更为严格的限制,nodeJs v17 之前版本没影响,但 V17 和之后版本会出现这个错误…...
Python|GIF 解析与构建(5):手搓截屏和帧率控制
目录 Python|GIF 解析与构建(5):手搓截屏和帧率控制 一、引言 二、技术实现:手搓截屏模块 2.1 核心原理 2.2 代码解析:ScreenshotData类 2.2.1 截图函数:capture_screen 三、技术实现&…...
铭豹扩展坞 USB转网口 突然无法识别解决方法
当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…...
大话软工笔记—需求分析概述
需求分析,就是要对需求调研收集到的资料信息逐个地进行拆分、研究,从大量的不确定“需求”中确定出哪些需求最终要转换为确定的“功能需求”。 需求分析的作用非常重要,后续设计的依据主要来自于需求分析的成果,包括: 项目的目的…...
突破不可导策略的训练难题:零阶优化与强化学习的深度嵌合
强化学习(Reinforcement Learning, RL)是工业领域智能控制的重要方法。它的基本原理是将最优控制问题建模为马尔可夫决策过程,然后使用强化学习的Actor-Critic机制(中文译作“知行互动”机制),逐步迭代求解…...
django filter 统计数量 按属性去重
在Django中,如果你想要根据某个属性对查询集进行去重并统计数量,你可以使用values()方法配合annotate()方法来实现。这里有两种常见的方法来完成这个需求: 方法1:使用annotate()和Count 假设你有一个模型Item,并且你想…...
MySQL 8.0 OCP 英文题库解析(十三)
Oracle 为庆祝 MySQL 30 周年,截止到 2025.07.31 之前。所有人均可以免费考取原价245美元的MySQL OCP 认证。 从今天开始,将英文题库免费公布出来,并进行解析,帮助大家在一个月之内轻松通过OCP认证。 本期公布试题111~120 试题1…...
大语言模型(LLM)中的KV缓存压缩与动态稀疏注意力机制设计
随着大语言模型(LLM)参数规模的增长,推理阶段的内存占用和计算复杂度成为核心挑战。传统注意力机制的计算复杂度随序列长度呈二次方增长,而KV缓存的内存消耗可能高达数十GB(例如Llama2-7B处理100K token时需50GB内存&a…...
JVM 内存结构 详解
内存结构 运行时数据区: Java虚拟机在运行Java程序过程中管理的内存区域。 程序计数器: 线程私有,程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都依赖这个计数器完成。 每个线程都有一个程序计数…...
实战三:开发网页端界面完成黑白视频转为彩色视频
一、需求描述 设计一个简单的视频上色应用,用户可以通过网页界面上传黑白视频,系统会自动将其转换为彩色视频。整个过程对用户来说非常简单直观,不需要了解技术细节。 效果图 二、实现思路 总体思路: 用户通过Gradio界面上…...
消息队列系统设计与实践全解析
文章目录 🚀 消息队列系统设计与实践全解析🔍 一、消息队列选型1.1 业务场景匹配矩阵1.2 吞吐量/延迟/可靠性权衡💡 权衡决策框架 1.3 运维复杂度评估🔧 运维成本降低策略 🏗️ 二、典型架构设计2.1 分布式事务最终一致…...
