数据结构:二叉树的链式结构及相关算法详解
目录
一.链式结构的实现
1.二叉树结点基本结构,初始化与销毁:
二.链式结构二叉树的几种遍历算法
1.几种算法的简单区分:
2.前序遍历:
3.中序遍历:
4.后序遍历:
5.层序遍历(广度优先遍历BFS):
三.链式二叉树的几种使用
1.计算树的结点个数:
2.计算树的叶子结点个数:
3.计算树的第k层结点个数:
4.计算树的深度:
5.查找结点为X的结点:
6.判断二叉树是否为完全二叉树:
一.链式结构的实现
1.二叉树结点基本结构,初始化与销毁:
(1)⽤链表来表示⼀棵⼆叉树,即⽤链来指示元素的逻辑关系。通常的⽅法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别⽤来给出该结点左孩子和右孩子所在的链结点的存储地址, 其结构如下:
(2)二叉树结点的初始化与树的创建:
#include"Tree.h" //结点的初始化 BTNode* buyNode(char x) {BTNode* node = (BTNode*)malloc(sizeof(BTNode));node->data = x;node->left = node->right = NULL;return node; } //树结构的创建 BTNode* creatBinaryTree() {BTNode* nodeA = buyNode('A');BTNode* nodeB = buyNode('B');BTNode* nodeC = buyNode('C');BTNode* nodeD = buyNode('D');BTNode* nodeE = buyNode('E');BTNode* nodeF = buyNode('F');nodeA->left = nodeB;nodeA->right = nodeC;nodeB->left = nodeD;nodeC->right = nodeE;nodeC->right = nodeF; }(3)链式二叉树的销毁:
(使用后序遍历,我在下文会写出具体操作)
void BinaryTreeDestory(BTNode** root) {//这里因为要改变根节点,应该传入的是根节点的地址,所以得拿二级指针接收//递归出口if ((*root) == NULL){return;}//自叶向根方向的释放//如果先释放的话,就找不到叶子节点了BinaryTreeDestory(&((*root)->left));BinaryTreeDestory(&((*root)->right));free(*root);*root = NULL; }
二.链式结构二叉树的几种遍历算法
1.几种算法的简单区分:
举一个普遍的例子具体说明一下,如图:
2.前序遍历:
简单说明一下前序遍历的基本逻辑:
假设一个树:
A
/ \
B C
/ \
D E那么其遍历过程为:
访问根节点 A 。
递归遍历左子树(以 B 为根):
访问 B
递归遍历左子树(以 D 为根):
访问 D
递归遍历右子树(以 E 为根):
访问 E
递归遍历右子树(以 C 为根):
访问 C
//前序遍历
void preOrder(BTNode* root)
{//头 左 右//递归出口if (root == NULL){printf("NULL");return;}printf("%c",root->data);preOrder(root->left);preOrder(root->right);
}
3.中序遍历:
void inOrder(BTNode* root)
{//左 头 右//递归出口if (root == NULL){printf("NULL");return;}inOrder(root->left); //注意别调用错了,调用中序的printf("%c", root->data);inOrder(root->right);
}
4.后序遍历:
void postOrder(BTNode* root)
{//递归出口if (root == NULL){printf("NULL");return;}postOrder(root->left);postOrder(root->right);printf("%c", root->data);
}
总结:如上述所示,前中后序遍历的代码共同点也是相当明显了,主要就是递归顺序左右子树的顺序不同而影响的代码输出顺序不同,其根本上来说就是函数栈帧的不断进行嵌套式的创建与销毁,如果挨个遍历可能会显得比较复杂,但通过代码所示的这种递归算法,前中后序的遍历实现也显得十分简洁明了
但还有一点尤其需要注意:就是不同于层序遍历,我上面所说的三种遍历都属于深度优先遍历(DFS),而层序遍历却属于广度优先遍历,关于这两大类遍历的优劣我会在实现层序遍历后作详细介绍
5.层序遍历(广度优先遍历BFS):
思路:
借助数据结构:队列,先通过根节点入队列,再循环判断队列是否为空,不为空则取队头然后出队头,并将队头结点的左右孩子入队列
(由于后续层序遍历的实现需要用到好些队列的知识,所有我先将队列的一些简单用法附在下面,需要的可以稍微看看)
//定义结点结构 typedef int QDataTpe; typedef struct QueueNode {struct QueneNode* next;QDataTpe data; }QueueNode;//定义队列的结构 typedef struct Queue {QueueNode* phead;//队头QueueNode* ptail;//队尾int size; }Queue;BTNode* buyNode(char x);//队列初始化 void QueueInit(Queue* pq) {assert(pq);pq->phead = pq->ptail = NULL; }//入队(从队尾入) void QueuePush(Queue* pq, QDataTpe x) {assert(pq);//申请一个结点QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));if (newnode == NULL){perror("malloc failed");exit(1);}newnode->data = x;newnode->next = NULL;//队列为空,newnode是对头也是队尾if (pq->phead == NULL){pq->phead = pq->ptail = newnode;}else //队列非空,尾插{pq->ptail->next = newnode;pq->ptail = pq->ptail->next;}pq->size++; } //判断队列是否为空 bool QueueEmpty(Queue* pq) {assert(pq);return pq->phead == 0; }//出队(从队头出) void QueuePop(Queue* pq) {assert(!QueueEmpty(pq));//只有一个结点的情况下,要把队尾和队头的两个指针都考虑到if (pq->phead == pq->ptail){free(pq->ptail);pq->phead = pq->ptail = NULL;}QueueNode* next = pq->phead->next;free(pq->phead);pq->phead = next; pq->size--; }//取队头数据 QDataTpe QueueFront(Queue* pq) {assert(!QueueEmpty(pq));return pq->phead->data; } //取队尾数据 QDataTpe QueueBack(Queue* pq) {assert(!QueueEmpty(pq));return pq->ptail->data; }//队列的销毁 void QueueDestory(Queue* pq) {assert(pq);QueueNode* pcur = pq->phead;while (pcur){QueueNode* next = pcur->next;free(pcur);pcur = next;}pq->phead = pq->ptail = NULL; }//取队列元素个数 int QueueSize(Queue* pq) {return pq->size; }
//层序遍历
void LeveIOrder(BTNode* root)
{Queue q;//创建一个队列QueueInit(&q);QueuePush(&q, root);while (!QueueEmpty(&q)){//取队头出队头,打印结点值BTNode* top = QueueFront(&q);QueuePop(&q);printf("%c", top->data);//将队头结点的非空左右孩子结点入队列if (top->left){QueuePush(&q, top->left);}if (top->right){QueuePush(&q, top->right);}QueuDestroy(&q);}}
小结:
DFS 优点(深度优先遍历)
节点少时效率高,节省内存
适合需要“全探索”或“找到任意解即可”的场景(如迷宫路径)
DFS 缺点
可能陷入无限循环(需记录已访问节点)
不保证最短路径(除非剪枝优化)
BFS 优点 (广度优先遍历)
保证找到最短路径(无权图中)
层级分明,易于理解
BFS 缺点
内存占用大(需存储整个层级节点)
不适合大规模数据或深层结构(如树深度极大)
三.链式二叉树的几种使用
1.计算树的结点个数:
法一:把size作为一个函数的形参,然后把这个树遍历一遍,每遍历一个节点就size(节点个数)加一,但需要注意的是,需要传入size的地址才能改变size的值
void BinaryTreeSize(BTNode* root,int* size) {if (root == NULL){return;}(*size)++;BinaryTreeSize(root->left,size);BinaryTreeSize(root->right, size); }法二:递归, 节点个数=左子树节点个数+右子树节点个数,所以我们以此为基础递归就可以了
int BinaryTreeSize(BTNode* root) {//节点个数=左子树节点个数+右子树节点个数//递归出口if (root == NULL){return 0;}return 1 + BinaryTreeSize(root->left) + BinaryTreeSize(root->right); }
2.计算树的叶子结点个数:
叶子结点:即没有左右孩子结点的结点4
int BinaryTreeLeafSize(BTNode* root) {//递归出口if (root == NULL){return 0;}if (root->left == NULL && root->right == NULL){return 1;}return BinaryTreeLeafSize(root->left) + BinaryTreeLeafSize(root->right); }
3.计算树的第k层结点个数:
如上图,当k=1时就是最底层结点,因此从最底层往上嵌套遍历就可以实现
int BinaryTreeLeafSize(BTNode* root) {//递归出口if (root == NULL){return 0;}if (root->left == NULL && root->right == NULL){return 1;}return BinaryTreeLeafSize(root->left) + BinaryTreeLeafSize(root->right); }
4.计算树的深度:
int BinaryTreeDeep(BTNode* root) {//计算树的深度if (root == NULL){return 0;}int lefDep = BinaryTreeDeep(root->left);int rigDep = BinaryTreeDeep(root->right);return 1 + (lefDep > rigDep ? lefDep : rigDep); }
5.查找结点为X的结点:
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{//递归出口if (root == NULL){return 0;}if (root->data == x){return root;}//代码走到这里证明根节点并不是我们要找的结点//接下来是左右子树各自分开递归搜查BTNode* left = BinaryTreeFind(root->left, x);if (left)//由于函数最后返回NULL,所以如果这个if条件可以进入就足以说明找到了需要的结点{return root;}BTNode* right = BinaryTreeFind(root->right, x);if (right){return root;}return NULL;
}
6.判断二叉树是否为完全二叉树:
// 判断⼆叉树是否是完全⼆叉树 bool BinaryTreeComplete(BTNode* root) {Queue q;QueueInit(&q);QueuePush(&q, root);while (!QueueEmpty(&q)){//取队头,出队头BTNode* top = QueueFront(&q);QueuePop(&q);if (top == NULL){break;}//队头结点的左右孩子入队列QueuePush(&q, top->left);QueuePush(&q, top->right);}//队列不为空,继续取队头出队头//1)队头存在非空结点----非完全二叉树//2)队头不存在非空结点----完全二叉树while (!QueueEmpty(&q)){BTNode* top = QueueFront(&q);QueuePop(&q);if (top != NULL){QueueDestroy(&q);return false;}}QueueDestroy(&q);return true; }第一个while是为了验证根结点,如果根结点为空,直接返回false,如果根结点不为空,就将树里的结点循环入队列然后继续将子结点入队列并且判断其是否为空,同样的第一次循环的的结束条件是当出现空为止,因此,当来到第二次循环时,树的结点里说明已经第一次循环出了空结点,而当树是完全二叉树时,第二次循环之后队列里应该全是空结点,因此,只要当第二次循环里出现非空结点时,就可以判断其时非完全二叉树(这题思路比较复杂,可以多想一会)
欧克,本次关于链式二叉树的知识点就到此为止了,相关的题目我也会在未来附上
ok,全文终
相关文章:
数据结构:二叉树的链式结构及相关算法详解
目录 一.链式结构的实现 1.二叉树结点基本结构,初始化与销毁: 二.链式结构二叉树的几种遍历算法 1.几种算法的简单区分: 2.前序遍历: 3.中序遍历: 4.后序遍历: 5.层序遍历(广度优先遍历B…...
10.【线性代数】—— 四个基本子空间
十、 四个基本子空间 1. 列空间 C ( A ) C(A) C(A) in R m R^m Rm2. 零空间 N ( A ) N(A) N(A) in R n R^n Rn3. 行空间 C ( A T ) C(A^T) C(AT) in R n R^n Rn4. 左零空间 N ( A T ) N(A^T) N(AT) in R m R^m Rm综述5. 新的向量空间 讨论矩阵 A m ∗ n A_{m*n} Am∗n…...
计算机黑皮书191本分享pdf
“黑皮书”通常指的是由机械工业出版社出版的计算机科学丛书。这些书籍的封面通常是黑色的,因此得名“黑皮书”。这些书籍涵盖了计算机科学的各个领域,包括操作系统、计算机网络、软件工程、编译原理、数据库等。 获取链接:链接:https://pan…...
MySQL Connector/J下载
MySQL Connector/J下载 下载mysql驱动jar包。 官网:https://downloads.mysql.com/archives/c-j/ 我下载的是8.0.33,下载的时候要注意与MySQL的版本对应。...
AIGC生图产品PM必须知道的Lora训练知识!
hihi,其实以前在方向AIGC生图技术原理和常见应用里面已经多次提到Lora的概念了,但是没有单独拿出来讲过,今天就耐心来一下! 🔥 一口气摸透AIGC文生图产品SD(Stable Diffusion)! 一、…...
【Swift 算法实战】城市天际线问题解法
网罗开发 (小红书、快手、视频号同名) 大家好,我是 展菲,目前在上市企业从事人工智能项目研发管理工作,平时热衷于分享各种编程领域的软硬技能知识以及前沿技术,包括iOS、前端、Harmony OS、Java、Python等…...
易错点abc
在同一个输入流上重复创建Scanner实例可能会导致一些问题,包括但不限于输入流的混乱。尤其是在处理标准输入(System.in)时,重复创建Scanner对象通常不是最佳实践,因为这可能导致某些输入数据丢失或者顺序出错。 为什么…...
C++ 正则表达式分组捕获入门指南
在 C 中,正则表达式(regex)是一种用于匹配字符串模式的强大工具。正则表达式不仅能帮助你查找符合特定模式的字符,还能捕获匹配的子字符串(即分组捕获)。这篇文章将介绍 C 正则表达式中的分组捕获机制&…...
AI人工智能机器学习之降维和数据压缩
1、概要 本篇学习AI人工智能机器学习之降维和数据压缩,以主成分分析(PCA, Principal Component Analysis)为例,从代码层面讲述机器学习中的降维和数据压缩。 2、降维和数据压缩 - 简介 在机器学习和数据分析中,降维&…...
17 款电脑压缩工具详解及下载指南(2025 年最新版)
在数字时代,文件压缩是日常工作与生活中不可或缺的操作。无论是视频剪辑师压缩视频以便上传,还是普通用户节省存储空间,一款优质的压缩软件都能极大提升效率。本文将详细介绍 17 款热门电脑压缩软件,涵盖它们的特点、下载地址及适用场景,助你找到最适合自己的工具。 一、…...
DeepSeek开源周Day5压轴登场:3FS与Smallpond,能否终结AI数据瓶颈之争?
2025年2月28日,DeepSeek开源周迎来了第五天,也是本次活动的收官之日。自2月24日启动以来,DeepSeek团队以每天一个开源项目的节奏,陆续向全球开发者展示了他们在人工智能基础设施领域的最新成果。今天,他们发布了Fire-F…...
ROS2软件调用架构和机制解析:Publisher创建
术语 DDS (Data Distribution Service): 用于实时系统的数据分发服务标准,是ROS 2底层通信的基础RMW (ROS Middleware): ROS中间件接口,提供与具体DDS实现无关的抽象APIQoS (Quality of Service): 服务质量策略,控制通信的可靠性、历史记录、…...
【落羽的落羽 C++】C++入门基础·其之一
文章目录 一、C简介1. C的发展历史2. C参考文档 二、namespace命名空间1. C语言的一个缺陷2. namespace3. 命名空间的使用3.1 命名空间成员访问3.2 using展开 一、C简介 1. C的发展历史 C起源于1979年的贝尔实验室,Bjarne Stroustrup(本贾尼博士&#…...
docker使用代理的简单配置
1准备代理服务器 准备代理服务器,例如192.168.120.168:52209 配置docker.service文件 查看service文件的位置 systemctl status docker 编辑service文件 vim /usr/lib/systemd/system/docker.service 添加代理配置 ... [Service] Environment"HTTP_PROXY…...
每日一题-设计食物评分系统,哈希表的有效使用
本题出自LeetCode2353.设计食物评分系统,连着一星期都是设计类的题目哈 题目 设计一个支持下述操作的食物评分系统: 修改 系统中列出的某种食物的评分。返回系统中某一类烹饪方式下评分最高的食物。 实现 FoodRatings 类: FoodRatings(Strin…...
大模型应用:多轮对话(prompt工程)
概述 在与大型语言模型(如ChatGPT)交互的过程中,我们常常体验到与智能助手进行连贯多轮对话的便利性。那么,当我们开启一个新的聊天时,系统是如何管理聊天上下文的呢? 一、初始上下文的建立 1. 创建新会…...
WSDM24-因果推荐|因果去偏的可解释推荐系统
1 动机 可解释推荐系统(ERS)通过提供透明的推荐解释,提高用户信任度和系统的说服力,如下图所示,然而: 1:现有工作主要关注推荐算法的去偏(流行度偏差),但未显…...
VScode在Windows11中配置MSVC
因为MSVC编译器在vs当中,所以我们首先要安装vs的一部分组件。如果只是需要MSVC的话,工作负荷一个都不需要勾选,在单个组件里面搜索MSVC和windows11 SDK,其中一个是编译器,一个是头文件然后右下角安装即可。搜索Develop…...
数据库基础二(数据库安装配置)
打开MySQL官网进行安装包的下载 https://www.mysql.com/ 接着找到适用于windows的版本 下载版本 直接点击下载即可 接下来对应的内容分别是: 1:安装所有 MySQL 数据库需要的产品; 2:仅使用 MySQL 数据库的服务器; 3&a…...
cuda-12.4.0 devel docker 中源码安装 OpenAI triton
1,准备 docker 容器 下载docker image: $ sudo docker pull nvidia/cuda:12.6.2-devel-ubuntu20.04 创建容器: sudo docker run --gpus all -it --name cuda_LHL_01 -v /home/hongleili/ex_triton/tmp1:/root/ex_triton/tmp1 nvidia/cuda:12.6…...
理解 MCP 工作流:使用 Ollama 和 LangChain 构建本地 MCP 客户端
🌟 什么是 MCP? 模型控制协议 (MCP) 是一种创新的协议,旨在无缝连接 AI 模型与应用程序。 MCP 是一个开源协议,它标准化了我们的 LLM 应用程序连接所需工具和数据源并与之协作的方式。 可以把它想象成你的 AI 模型 和想要使用它…...
Leetcode 3577. Count the Number of Computer Unlocking Permutations
Leetcode 3577. Count the Number of Computer Unlocking Permutations 1. 解题思路2. 代码实现 题目链接:3577. Count the Number of Computer Unlocking Permutations 1. 解题思路 这一题其实就是一个脑筋急转弯,要想要能够将所有的电脑解锁&#x…...
Golang dig框架与GraphQL的完美结合
将 Go 的 Dig 依赖注入框架与 GraphQL 结合使用,可以显著提升应用程序的可维护性、可测试性以及灵活性。 Dig 是一个强大的依赖注入容器,能够帮助开发者更好地管理复杂的依赖关系,而 GraphQL 则是一种用于 API 的查询语言,能够提…...
ffmpeg(四):滤镜命令
FFmpeg 的滤镜命令是用于音视频处理中的强大工具,可以完成剪裁、缩放、加水印、调色、合成、旋转、模糊、叠加字幕等复杂的操作。其核心语法格式一般如下: ffmpeg -i input.mp4 -vf "滤镜参数" output.mp4或者带音频滤镜: ffmpeg…...
【JavaSE】绘图与事件入门学习笔记
-Java绘图坐标体系 坐标体系-介绍 坐标原点位于左上角,以像素为单位。 在Java坐标系中,第一个是x坐标,表示当前位置为水平方向,距离坐标原点x个像素;第二个是y坐标,表示当前位置为垂直方向,距离坐标原点y个像素。 坐标体系-像素 …...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...
【数据分析】R版IntelliGenes用于生物标志物发现的可解释机器学习
禁止商业或二改转载,仅供自学使用,侵权必究,如需截取部分内容请后台联系作者! 文章目录 介绍流程步骤1. 输入数据2. 特征选择3. 模型训练4. I-Genes 评分计算5. 输出结果 IntelliGenesR 安装包1. 特征选择2. 模型训练和评估3. I-Genes 评分计…...
【Java学习笔记】BigInteger 和 BigDecimal 类
BigInteger 和 BigDecimal 类 二者共有的常见方法 方法功能add加subtract减multiply乘divide除 注意点:传参类型必须是类对象 一、BigInteger 1. 作用:适合保存比较大的整型数 2. 使用说明 创建BigInteger对象 传入字符串 3. 代码示例 import j…...
推荐 github 项目:GeminiImageApp(图片生成方向,可以做一定的素材)
推荐 github 项目:GeminiImageApp(图片生成方向,可以做一定的素材) 这个项目能干嘛? 使用 gemini 2.0 的 api 和 google 其他的 api 来做衍生处理 简化和优化了文生图和图生图的行为(我的最主要) 并且有一些目标检测和切割(我用不到) 视频和 imagefx 因为没 a…...
Netty从入门到进阶(二)
二、Netty入门 1. 概述 1.1 Netty是什么 Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients. Netty是一个异步的、基于事件驱动的网络应用框架,用于…...










