数据结构:二叉树的链式结构及相关算法详解
目录
一.链式结构的实现
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…...
零门槛NAS搭建:WinNAS如何让普通电脑秒变私有云?
一、核心优势:专为Windows用户设计的极简NAS WinNAS由深圳耘想存储科技开发,是一款收费低廉但功能全面的Windows NAS工具,主打“无学习成本部署” 。与其他NAS软件相比,其优势在于: 无需硬件改造:将任意W…...
python打卡day49
知识点回顾: 通道注意力模块复习空间注意力模块CBAM的定义 作业:尝试对今天的模型检查参数数目,并用tensorboard查看训练过程 import torch import torch.nn as nn# 定义通道注意力 class ChannelAttention(nn.Module):def __init__(self,…...
高频面试之3Zookeeper
高频面试之3Zookeeper 文章目录 高频面试之3Zookeeper3.1 常用命令3.2 选举机制3.3 Zookeeper符合法则中哪两个?3.4 Zookeeper脑裂3.5 Zookeeper用来干嘛了 3.1 常用命令 ls、get、create、delete、deleteall3.2 选举机制 半数机制(过半机制࿰…...
智能在线客服平台:数字化时代企业连接用户的 AI 中枢
随着互联网技术的飞速发展,消费者期望能够随时随地与企业进行交流。在线客服平台作为连接企业与客户的重要桥梁,不仅优化了客户体验,还提升了企业的服务效率和市场竞争力。本文将探讨在线客服平台的重要性、技术进展、实际应用,并…...
省略号和可变参数模板
本文主要介绍如何展开可变参数的参数包 1.C语言的va_list展开可变参数 #include <iostream> #include <cstdarg>void printNumbers(int count, ...) {// 声明va_list类型的变量va_list args;// 使用va_start将可变参数写入变量argsva_start(args, count);for (in…...
BLEU评分:机器翻译质量评估的黄金标准
BLEU评分:机器翻译质量评估的黄金标准 1. 引言 在自然语言处理(NLP)领域,衡量一个机器翻译模型的性能至关重要。BLEU (Bilingual Evaluation Understudy) 作为一种自动化评估指标,自2002年由IBM的Kishore Papineni等人提出以来,…...
华为OD机试-最短木板长度-二分法(A卷,100分)
此题是一个最大化最小值的典型例题, 因为搜索范围是有界的,上界最大木板长度补充的全部木料长度,下界最小木板长度; 即left0,right10^6; 我们可以设置一个候选值x(mid),将木板的长度全部都补充到x,如果成功…...
抽象类和接口(全)
一、抽象类 1.概念:如果⼀个类中没有包含⾜够的信息来描绘⼀个具体的对象,这样的类就是抽象类。 像是没有实际⼯作的⽅法,我们可以把它设计成⼀个抽象⽅法,包含抽象⽅法的类我们称为抽象类。 2.语法 在Java中,⼀个类如果被 abs…...
k8s从入门到放弃之HPA控制器
k8s从入门到放弃之HPA控制器 Kubernetes中的Horizontal Pod Autoscaler (HPA)控制器是一种用于自动扩展部署、副本集或复制控制器中Pod数量的机制。它可以根据观察到的CPU利用率(或其他自定义指标)来调整这些对象的规模,从而帮助应用程序在负…...
Vue3中的computer和watch
computed的写法 在页面中 <div>{{ calcNumber }}</div>script中 写法1 常用 import { computed, ref } from vue; let price ref(100);const priceAdd () > { //函数方法 price 1price.value ; }//计算属性 let calcNumber computed(() > {return ${p…...










