【数据结构】二叉树链式结构——感受递归的暴力美学

前言:

        在上篇文章【数据结构】二叉树——顺序结构——堆及其实现中,实现了二叉树的顺序结构,使用堆来实现了二叉树这样一个数据结构;现在就来实现而二叉树的链式结构。

一、链式结构

        链式结构,使用链表来表示一颗二叉树,即用链来指示二叉树中元素的逻辑关系。通常的就是链表中每个节点右三个域组成,数据域左右指针域左右指针分别指向该节点的左孩子节点和右孩子节点的存储地址。

        链式结构分为二叉链和三叉链(三叉链比而二叉链多一个指向父节点的指针域)。

这里使用二叉链来实现。

二、二叉树链式结构实现相关功能

在实现之前,先来看一下,具体要实现哪些功能?

        首先就是二叉树的结构,我们使用链表来实现,链表的每一个节点都由数值域和左右指针域组成。

        二叉树链式结构的节点

typedef int TreeDataType;
typedef struct TreeNode
{TreeDataType data;struct TreeNode* left;struct TreeNode* right;
}TreeNode;

        二叉树链式结构如上,接下来再来看一下二叉树链式结构实现的相关功能:

//二叉树的前序排序
int PreOrder(TreeNode* root);
//二叉树的中序排序
int InOrder(TreeNode* root);
//二叉树的后序遍历
int AfterOrter(TreeNode* root);
//二叉树节点个数
int BinaryTreeSize(TreeNode* root);
//二叉树叶子节点个数
int BinaryTreeLeafSize(TreeNode* root);
//二叉树第k层节点个数
int BinaryTreeLevelKSize(TreeNode* root, int k);
//二叉树的深度
int BinaryTreeDepth(TreeNode* root);
//查找二叉树值为x的节点
TreeNode* BinaryTreeFind(TreeNode* root, TreeDataType x);
//层序遍历
void LevelOrder(BTNode* root)
//判断是否为满二叉树
bool BinaryTreeComplete(BTNode* root)
//二叉树的销毁
void TreeDesTroy(TreeNode** root);

        2.1、二叉树前、中、后序遍历

这里,前序、中序和后序遍历什么意思呢,

        说白了,这三种遍历其实就是以对二叉树访问的顺序,来遍历二叉树

这里以前序遍历为例,遍历二叉树

现在有一个这样的二叉树,用前序遍历来访问这颗二叉树(这里访问并输出访问到的数据)

        

首先访问根节点,输出访问到的数据 1 ;再访问左子树

再访问其左子树的根节点,输出访问到的数据  2 ;再接着访问左子树

再访问其左子树的根节点,输出访问到的数据 4 ;再接着访问左子树

此时访问到了空节点,就直接回退,回退到其父节点的函数栈帧,访问父节点的右子树

        访问4这个节点的右子树,也为空,此时节4这个点为根节点的左子树已经访问 结束了,回退到其父节点的函数栈帧,访问其父节点的右子树

        访问2这个节点的右子树,先输出右子树的根节点数据 5 ;再访问根节点5的左子树

        这里节点5的左右节点都为空,这里省略一些过程;此时5这个节点访问结束,回退到2这个节点,而2这个节点右子树访问结束,也已经访问结束了;此时再回退到根节点,访问根节点的右子树。

        此时访问根节点的右子树的根节点,输出访问到的数据 3 ;再接着访问3这个节点的左子树

        这里,3这个节点的左右节点都为空,访问就直接返回,回退到根节点,就说明根节点的右子树访问已经结束,此时二叉树已经变了结束。

        这里前序遍历的输出结果为 1 2 4 5 3

这三个前、中、后序遍历本质上没啥差别,这里直接看实现代码

//二叉树的前序排序
void PreOrder(TreeNode* root)
{if (root == NULL){return;}//输出数据printf("%d ", root->data);//遍历左子树PreOrder(root->left);//遍历右子树PreOrder(root->right);
}
//二叉树的中序排序
void InOrder(TreeNode* root)
{if (root == NULL){return;}//遍历左子树InOrder(root->left);//输出数据printf("%d ", root->data);//遍历右子树InOrder(root->right);
}
//二叉树的后序遍历
void AfterOrter(TreeNode* root)
{if (root == NULL){return;}//遍历左子树AfterOrter(root->left);//遍历右子树AfterOrter(root->right);//输出数据printf("%d ", root->data);
}

        这里我们来测试代码对不对,我们先创建一个二叉树,然后看输出结果是否正确、

TreeNode* BuyNode(TreeDataType x)
{TreeNode* newnode = (TreeNode*)malloc(sizeof(TreeNode));if (newnode == NULL){perror("malloc");exit(1);}newnode->data = x;newnode->left = newnode->right = NULL;return newnode;
}void test()
{TreeNode* node1 = BuyNode(1);TreeNode* node2 = BuyNode(2);TreeNode* node3 = BuyNode(3);TreeNode* node4 = BuyNode(4);TreeNode* node5 = BuyNode(5);node1->left = node2;node1->right = node3;node2->left = node4;node2->right = node5;//前序遍历PreOrder(node1);printf("\n");//中序遍历InOrder(node1);printf("\n");//后序遍历AfterOrter(node1);printf("\n");
}
int main()
{test();return 0;
}

这里代码没有问题。

        2.2、二叉树节点个数

        这里,二叉树的链式结构我们是使用递归来实现的,那我们该如何去计算二叉树的节点个数呢?

思路:
        遍历二叉树,遍历到空节点就返回0,不是空节点就返回其左子树和右子树节点个数之和再加一

代码如下:

//二叉树节点个数
int BinaryTreeSize(TreeNode* root)
{if (root == NULL){return 0;}return BinaryTreeSize(root->left) + BinaryTreeSize(root->right) + 1;
}

        2.3、二叉树叶子结点个数

        所谓叶子节点:就是左右子节点都为空的节点,我们就利用这个特点来判断

思路:

        遍历二叉树,遍历到空节点就返回空;遍历到节点的左右节点都为空,返回1;否则就返回节点的左子树和右子树的叶子结点之和。

代码如下:

//二叉树叶子节点个数
int BinaryTreeLeafSize(TreeNode* root)
{if (root == NULL){return 0;}if (root->left == NULL && root->right == NULL){return 1;}return BinaryTreeLeafSize(root->left) + BinaryTreeLeafSize(root->right);
}

        2.4、二叉树第k层节点个数

        求二叉树第k层节点的个数,我们先来看这样的一个图

        通过图我们可以看到,每次向下遍历一层,k-1;当k=1时,正好是第k层

思路:

        遍历二叉树,遍历到空节点就返回 0 ;遍历到k=1时,就返回1;否则就返回该节点左子树和右子树中第k - 1层的节点个数。

这里需要注意,要在判断k=1之前判断节点是否为空。

代码如下:

//二叉树第k层节点个数
int BinaryTreeLevelKSize(TreeNode* root, int k)
{if (root == NULL){return 0;}if (k == 1){return 1;}return BinaryTreeLevelKSize(root->left, k - 1) + BinaryTreeLevelKSize(root->right, k - 1);
}

        2.5、二叉树的深度

        求二叉树的深度,我们这里是将二叉树的左子树和右子树分开依次遍历的,显然我们不能像上面一样,返回遍历左子树和右子树的和;这里就返回其中最大的。

思路:

        遍历二叉树,遍历到空节点就返回空;定义两个值,记录其节点左子树和右子树的深度再加一;返回其中最大的即可。

理解:这里定义两个值记录其左右子树的深度加一(加一就是记录当前这一层)。

代码如下:

//二叉树的深度
int BinaryTreeDepth(TreeNode* root)
{if (root == NULL){return 0;}int depth_left = BinaryTreeDepth(root->left) + 1;int depth_right = BinaryTreeDepth(root->right) + 1;return (depth_left > depth_right) ? depth_left : depth_right;
}

        2.6、查找二叉树中值为 x 的节点

        查找相对来说就比较简单了,遍历二叉树,为空就返回NULL,值为x就返回该节点的地址,如果遍历过程中函数返回值不为NULL,就证明找到了,直接返回即可。

思路:

        遍历二叉树,遍历到空节点就返回NULL;判断节点的值是否为x,是就返回该节点的地址;不是。就创建指针变量接受其左右子树遍历的结果,如果不为NULL就直接返回该返回值。为空则继续遍历。

代码如下:

//查找二叉树值为x的节点
TreeNode* BinaryTreeFind(TreeNode* root, TreeDataType x)
{if (root == NULL){return NULL;}if (root->data == x){return root;}TreeNode* pleft = BinaryTreeFind(root->left, x);if (pleft)return pleft;TreeNode* pright = BinaryTreeFind(root->right, x);if (pright)return pright;return NULL;
}

到这里,来看一下这些代码是否正确

        对于这样的一个二叉树,代码输出结果都正确。

        2.7、层序遍历

这里层序遍历,我们需要借用队列这样一个数据结构(前面有详细讲解),

大致思路如此:

将根节点数据放入队列中;然后出队列,并且把该节点的左右子节点插入到队列当中。

将1这个节点出队列,再把左右子节点插入到队列当中

再出队头节点,将其左右子节点插入队列当中

依次类推,当队头数据左右子节点为空,就不进插入队列这一操作。

代码如下:

        这里用到了对列相关代码,详细代码在上篇

//层序遍历
//借助数据结构---队列
void LevelOrder(BTNode* root)
{Queue q;QueueInit(&q);QueuePush(&q, root);while (!QueueEmpty(&q)){//取队头,打印BTNode* front = QueueFront(&q);printf("%d ", front->data);QueuePop(&q);//队头节点的左右孩子入队列if (front->left)QueuePush(&q, front->left);if (front->right)QueuePush(&q, front->right);}//队列为空QueueDestroy(&q);
}

        2.8、判断二叉树是否为满二叉树

        判断二叉树是否为满二叉树,还是借用队列这样的数据结构,和层序遍历有相似之处;与层序遍历不同的是,这里,即便左右子节点为空,有进行入队列操作;当队头数据为空,就判断队列剩余数据是否都为空,如果都为空,就证明二叉树为满二叉树;否则就不是满二叉树。

代码如下:

//判断二叉树是否为完全二叉树
//---队列
bool BinaryTreeComplete(BTNode* root)
{Queue q;QueueInit(&q);QueuePush(&q, root);while (!QueueEmpty(&q)){BTNode* front = QueueFront(&q);QueuePop(&q);if (front == NULL){break;}QueuePush(&q, front->left);QueuePush(&q, front->right);}//队列不一定为空while (!QueueEmpty(&q)){BTNode* front = QueueFront(&q);QueuePop(&q);if (front != NULL){QueueDestroy(&q);return false;}}QueueDestroy(&q);return true;
}

        2.9、二叉树的销毁

        这里为了测试这些代码,就手动创建了一个二叉树,这些都是动态开辟的空间,养成好习惯,手动释放动态开辟的空间。

这里需要注意的是:使用后序遍历,先是否底层的节点,

代码如下:

// 二叉树销毁
void BinaryTreeDesTroy(TreeNode** root)
{if (*root == NULL){return;}BinaryTreeDesTroy(&((*root)->left));BinaryTreeDesTroy(&((*root)->right));free(*root);*root = NULL;
}

感谢各位大佬支持并指出问题,

                        如果本篇内容对你有帮助,可以一键三连支持以下,感谢支持!!!

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

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

相关文章

FPGA:有限状态机

从以下6个实验理解状态机的概念 开发板频率为 50 M H z 50MHz 50MHz,一个时钟周期是 20 n s 20ns 20ns。 1、实验一:LED灯亮0.25秒、灭0.75秒的状态循环 通过之前的分析,我们实现频闪灯时,是让led灯在0.5秒实现一次翻转,而这里…

经典文献阅读之--World Models for Autonomous Driving(自动驾驶的世界模型:综述)

Tip: 如果你在进行深度学习、自动驾驶、模型推理、微调或AI绘画出图等任务,并且需要GPU资源,可以考虑使用UCloud云计算旗下的Compshare的GPU算力云平台。他们提供高性价比的4090 GPU,按时收费每卡2.6元,月卡只需要1.7元每小时&…

html+css 实现水波纹按钮

前言:哈喽,大家好,今天给大家分享htmlcss 绚丽效果!并提供具体代码帮助大家深入理解,彻底掌握!创作不易,如果能帮助到大家或者给大家一些灵感和启发,欢迎收藏关注哦 💕 文…

vue3前端开发-小兔鲜项目-使用pinia插件完成token的本地存储

vue3前端开发-小兔鲜项目-使用pinia插件完成token的本地存储!实际业务开发中,token是一个表示着用户登录状态的重要信息,它有自己的生命周期。因此,这个参数值必须实例化存储在本地中。不能跟着pinia。因为pinia是基于内存设计的模…

事务和函数索引

事务 事务的定义 事务(Transaction),就是将一组SQL语句放在同一批次内去执行,如果一个SQL语句出错,则该批次内 的所有SQL都将被取消执行。 事务的特点 一个事务中如果有一个数据库操作失败,那么整个 事务…

若依框架 : 生成代码

6.生成代码 6.1.配置生成设置 ruoyi-generator -> src -> main -> resources -> generator.yml 由于 案例中 表都有 前缀 为 tta_ , 这里设置去掉 6.2.生成代码 6.2.1.导入数据库中的表 6.2.2.修改设置 6.2.2.1.设置生成信息 点击 编辑 -> 生成信息 特别…

m4a怎么转mp3?m4a转mp3的几种方法教程

m4a怎么转mp3?M4A音频格式的全称MPEG-4 Audio,是一种音频压缩格式。这种格式以其卓越的音质和相对较小的文件大小而广受欢迎,尤其是在音乐存储、在线流媒体以及音频编辑等领域。M4A格式被广泛应用于苹果公司的产品中,如iPhone、iP…

开放式耳机会成为未来的主流吗?开放式耳机推荐指南

开放式耳机是否会成为未来的主流,是一个值得探讨的问题。 从目前的市场趋势和技术发展来看,有一些因素支持开放式耳机可能成为主流。 一方面,人们对于健康和舒适的关注度不断提高。长时间佩戴传统耳机可能导致耳部不适,而开放式…

在Linux中,部署及优化Tomcat

tomcat概述 自 2017 年 11月编程语言排行榜 Java 占比 13%,高居榜首,Tomcat 也一度成为 Java开发人员的首选。其开源、占用系统资源少、跨平台等特性深受广大程序员喜爱。本章主要学习如何部署 Tomcat 服务,根据生产环境实现多个虚拟主机的配置&#xf…

【QGroundControl二次开发】五.python生成自定义MAVLink消息及使用

一 . 环境配置 参考&#xff1a; MAVLink代码生成-C# 二. 生成MAVLINK协议 在MAVlink源码下找到message_definitions/common.xml&#xff0c;修改其中的内容。 例如&#xff1a; <message id"12" name"DISTANCE_SENSOR"><description>Dedi…

【Code Complete2】Note-1 [启发式编程、管理复杂度、隐藏设计]

【Code Complete2】_Note-1 [启发式编程、管理复杂度、隐藏设计] 文章目录 【Code Complete2】_Note-1 [启发式编程、管理复杂度、隐藏设计]启发式编程管理复杂度隐藏设计--减少“改动所影响的代码量” 启发式编程 ​ **设计是一个启发的过程&#xff0c;充满了不确定性&#…

记录|cmd方式恢复U盘中的数据

目录 前言一、CMD恢复Step1.Step2. 更新时间 前言 参考文章&#xff1a; u盘数据误删怎么恢复&#xff1f;安利8款数据恢复软件免费版&#xff08;2024 NEW&#xff09; 回家后&#xff0c;家人说U盘里的歌突然没有了。我就用电脑看了看&#xff0c;发现电脑中能看到U盘中是满的…

修改启动方案

AMP设置为1Linux&#xff08;CPU0&#xff09; 3HAL&#xff08;CPU1、 2、 3&#xff09; 配置。 用vscode打开its 配置文件rk3568_amp_linux.its文件修改 /* SPDX-License-Identifier: BSD-3-Clause */ /** Copyright (c) 2022 Rockchip Electronics Co., Ltd.*//dts-v1/; /…

【优秀python算法毕设】基于python时间序列模型分析气温变化趋势的设计与实现

1 绪论 1.1 研究背景与意义 在气候变化日益受到全球关注的背景下&#xff0c;天气气温的变化已经对人们的生活各方面都产生了影响&#xff0c;人们在外出时大多都会在手机上看看天气如何&#xff0c;根据天气的变化来决定衣物的穿着和出行的安排。[1]如今手机能提供的信息已经…

【大模型】基于LoRA微调Gemma大模型(1)

文章目录 一、LoRA工作原理1.1 基本原理1.2 实现步骤 二、LoRA 实现2.1 PEFT库&#xff1a;高效参数微调LoraConfig类&#xff1a;配置参数 2.2 TRL库SFTTrainer 类 三、代码实现3.1 核心代码3.2 完整代码 参考资料 大模型微调技术有很多&#xff0c;如P-Tuning、LoRA 等&#…

操作系统杂项(八)

目录 一、简述互斥锁的机制&#xff0c;互斥锁与读写的区别 1、互斥锁机制 2、互斥锁和读写锁 二、简述信号量及其作用 1、概念 2、原理 3、作用 三、简述进程、线程的中断切换过程 1、进程上下文切换 2、线程上下文切换 四、简述自旋锁和互斥锁的使用场景 1、互斥…

宝塔单ip,新建多站点

报错如上&#xff1a; 那么如何新建多站点呢 先随便写个名字上去&#xff0c;然后再重新绑定别的端口… 这个时候访问99端口即可 。 如果是有域名&#xff0c;则不需要这样做 、直接80端口也可以多站点

数据缺失补全方法综述

数据缺失补全方法综述 摘要1. 引言2. 数据缺失的类型3. 数据缺失补全方法3.1 简单插补方法3.1.1 均值插补3.1.2 中位数插补3.1.3 众数插补3.1.4 前向填充和后向填充3.1.5 线性插值3.1.6 多重插补 3.2 基于模型的插补方法3.2.1 线性回归插补3.2.2 加权回归插补3.2.3 主成分分析&…

STM32智能工业监控系统教程

目录 引言环境准备智能工业监控系统基础代码实现&#xff1a;实现智能工业监控系统 4.1 数据采集模块 4.2 数据处理与控制模块 4.3 通信与网络系统实现 4.4 用户界面与数据可视化应用场景&#xff1a;工业监控与优化问题解决方案与优化收尾与总结 1. 引言 智能工业监控系统通…

Navicat premium最新【16/17 版本】安装下载教程,图文步骤详解(超简单,一步到位,免费下载领取)

文章目录 软件介绍软件下载安装步骤激活步骤 软件介绍 Navicat是一款快速、可靠且功能全面的数据库管理工具&#xff0c;专为简化数据库的管理及降低系统管理成本而设计。以下是对Navicat的详细介绍&#xff1a; 一、产品概述 开发目的&#xff1a;Navicat旨在通过其直观和设计…