数据结构——链式二叉树知识点以及链式二叉树数据操作函数详解!!

引言:该博客将会详细的讲解二叉树的三种遍历方法:前序、中序、后序,也同时会讲到关于二叉树的数据操作函数。值得一提的是,这些函数几乎都是建立在一个函数思想——递归之上的。这次的代码其实写起来十分简单,用不了几行就可以解决问题,但是其中的代码思想和运作方式才是难点。但只要我们搞懂了思想,手撕代码完全就没问题了,就让我们一起加油吧!

更多有关C语言和数据结构知识详解可前往个人主页:计信猫

目录

一,二叉树的遍历方式

0,三序前言

1,前序

2,中序

3,后序

二,二叉树的数据操作函数

1,二叉树节点个数

2,二叉树叶子节点个数

3,二叉树高度 

4,二叉树第k层节点个数

5,查找值为x的节点

6,二叉树的销毁

三,二叉树的层序遍历

1,  层序遍历

2,判断完全二叉树 

四,结语


一,二叉树的遍历方式

0,三序前言

        其实我们所说的三序其实就可以分为前序、中序、后序它们分别表示对一棵二叉树不同的遍历方式。我们在这里先对它们的遍历方式做一次总结:

●前序:根,左子树,右子树

●中序:左子树,根,右子树

●后序:左子树,右子树,根

        在这里,我们以如下的二叉树为例子进行三序的介绍:

        于是我们使用如下的代码在VS中直接手搓出上图的二叉树以方便我们后续对代码的检测

typedef int BTDataType;
//二叉树节点
typedef struct BinaryTreeNode
{BTDataType val;struct BinaryTreeNode* left;struct BinaryTreeNode* right;
}BTNode;
//创建节点
BTNode* BuyNode(BTDataType x)
{BTNode* newnode = (BTNode*)malloc(sizeof(BTNode));newnode->val = x;newnode->left = NULL;newnode->right = NULL;return newnode;
}
//创建二叉树
BTNode* CreatBinaryTree()
{//创建节点BTNode* node1 = BuyNode(1);BTNode* node2 = BuyNode(2);BTNode* node3 = BuyNode(3);BTNode* node4 = BuyNode(4);BTNode* node5 = BuyNode(5);BTNode* node6 = BuyNode(6);//将节点按照图示连起来node1->left = node2;node1->right = node4;node2->left = node3;node4->left = node5;node4->right = node6;//node1为根节点return node1;
}

         并且,我们仍然使用三文件操作法,如下图所示:

1,前序

 ●前序:根,左子树,右子树

        那么对于该二叉树,我们应该如何遍历呢?

         按照前序遍历的要求,我们就可以将一棵二叉树不断地划分为根、左子树、右子树

        只有在我们将node1的左子树遍历完之后我们才可以进入右子树node1的左子树又可以被视作以node2,并且包含左右子树的一棵新树,所以我们又需要对新的树进行前序遍历。如此循环下去,其实递归的思想也就慢慢体现出来了。

        所以,以上的被遍历完之后的结果如下所示,其中N表示NULL空节点:

         所以前序遍历的代码如下:

//前序遍历
void PreOrder(BTNode* root)
{if (root == NULL){printf("N ");return;}printf("%d ", root->val);PreOrder(root->left);PreOrder(root->right);
}

2,中序

●中序:左子树,根,右子树

        现在,我们以中序遍历法遍历如下二叉树:

        所以,我们还是将这棵二叉树不断地分解为根,左子树,右子树三个部分。但是我们这一次就会改变顺序,我们需要先遍历完左子树后再进行的遍历,最后才进行右子树的遍历。而以node1根节点左子树又可以继续被细分为以上三个部分,所以我们又需要进行中序遍历,直到某个节点的左子树被遍历完(为空),方可进行的遍历

        所以,以上的中序遍历完之后的结果如下:

        所以中序遍历的代码入下: 

//中序遍历
void MidOrder(BTNode* root)
{if (root == NULL){printf("N ");return;}MidOrder(root->left);printf("%d ", root->val);MidOrder(root->right);
}

3,后序

●后序:左子树,右子树,根

        如果我们使用后序遍历法遍历如下二叉树,又会是怎样一个结果呢?

        所以我们仍然以之前学到的方式进行递归类推先遍历完node1的左子树,后遍历node1的右子树,最后在遍历根node1。而在遍历node1的子树时,它的子树又可以被视为以node2node4根节点新树,故我们又对其再进行后序遍历,直到遇到空节点

        所以我们模拟遍历之后的结果如下图所示:

        那么,后序遍历的代码入下:

//后序遍历
void BackOrder(BTNode* root)
{if (root == NULL){printf("N ");return;}BackOrder(root->left);BackOrder(root->right);printf("%d ", root->val);
}

二,链式二叉树的数据操作函数

1,二叉树节点个数

        当我们想要知道一棵二叉树中有多少个节点的时候,我们就可以使用这个函数。

//求二叉树中的节点个数
int TreeSize(BTNode* root);

        在这个函数中,我们还是使用到了递归的思想。我们可以将一棵总节点个数分为一个根节点与它的左右两子树的节点和。然后它的子树又可以再次使用这个方法将它的子树拆分,直到遇到NULL递归结束,返回值为0。所以我们的代码如下:

//求二叉树中的节点个数
int TreeSize(BTNode* root)
{//遇到空,递归结束开始返回if (root == NULL){return 0;}//将二叉树拆分为根和左右两子树之和return TreeSize(root->right) + TreeSize(root->left) + 1;
}

2,二叉树叶子节点个数

        这个函数用于求的一棵二叉树之中的叶子节点的个数。

//求二叉树中叶子节点的个数
int TreeLeafSize(BTNode* root)

         首先,我们可以确定的是,当一个节点左右两子节点全为NULL时,那么这个节点就是叶节点而当我们想要找到一棵二叉树中的叶节点个数时,我们就可以将总的叶节点的个数拆分为左右两子树的叶节点之和。然后我们不停以以上方式进行拆分,直到找到叶节点递归结束,返回值为1。而要注意,空树叶节点个数为0,要特殊讨论。所以按照以上的思想进行代码实现如下:

//求二叉树中叶子节点的个数
int TreeLeafSize(BTNode* root)
{//空树没有叶节点if (root == NULL){return 0;}//找到了叶节点,递归结束,返回1if (root->left == NULL && root->right == NULL){return 1;}//开始递归return TreeLeafSize(root->left) + TreeLeafSize(root->right);
}

3,二叉树高度 

        二叉树的高度其实就是二叉树的,也就是它的最大高度

//求二叉树的高度
int TreeHeight(BTNode* root);

        当我们想要找到一棵树的最大高度的时候,其实我们就可以将这棵的高度拆分为左子树与右子树中高度的较大值加上1(其中1代表了根也占一层高度)。如此递归下去,当我们递归叶节点时,就递归结束,返回值为1。当然最后我们也需要注意,空树的高度为0。所以我们的代码如下:

//求二叉树的高度
int TreeHeight(BTNode* root)
{//空树高度为0if (root == NULL){return 0;}int left = TreeHeight(root->left);int right = TreeHeight(root->right);return left > right ? left + 1 : right + 1;
}

4,二叉树第k层节点个数

        该函数则专门用于求出一棵二叉树第k层节点个数

//求二叉树第k层的节点个数
int TreeLevelKSize(BTNode* root, int k);

        对于这个函数的实现,还是请出我们的老朋友——递归。对于一棵二叉树,求它第k层节点个数时,我们就可以将这个问题视为求其根节点的左右子树的(k-1)层的节点个数之和直到k持续减一,使k==1时,说明该层就是我们的第k层,此时递归结束,返回值为1就可以了。 所以我们的代码如下:

//求二叉树第k层的节点个数
int TreeLevelKSize(BTNode* root, int k)
{//空树则返回0if (root == NULL){return 0;}if (k == 1){return 1;}//根节点的左右子树的(k-1)层的节点个数之和return TreeLevelKSize(root->left, k - 1) + TreeLevelKSize(root->right, k - 1);
}

5,查找值为x的节点

        此函数用于查找二叉树中值为x的节点,并返回该节点的地址。

//查找值为x的节点
BTNode* TreeFind(BTNode* root, BTDataType x);

        既然我们想要找到值为x节点时,那遍历这个二叉树就必不可少了!所以我们就可以使用之前学到的三序遍历之一的前序遍历法我们先遍历,若根节点不是x节点我们就对它的左子树进行遍历,最后再对右子树遍历。若二叉树为空或者不存在x节点我们都返回NULL。所以我们的代码方法如下:

//查找值为x的节点
BTNode* TreeFind(BTNode* root, BTDataType x)
{//为空树就返回NULLif (root == NULL){return NULL;}//先遍历根节点if (root->val == x){return root;}//再遍历左子树BTNode* ret1 = TreeFind(root->left, x);if (ret1)//ret1不为空,说明x存在于左子树{return  ret1;}BTNode* ret2 = TreeFind(root->right, x);if (ret2)//ret2不为空,说明x存在于右子树{return ret2;}//整棵二叉树都不存在x节点,返回NULLreturn NULL;
}

6,二叉树的销毁

        当我们要结束对二叉树的操作时,我们就需要对二叉树进行销毁操作,防止内存泄漏。

//二叉树的销毁
void TreeDestroy(BTNode* root);

        在我们对二叉树进行遍历销毁时,我们就应该选择后序遍历,将根节点进行最后销毁 因为一旦将根节点先行销毁之后,那么我们就找不到根节点所对应的左右子树了,就无法进行后续销毁。所以代码如下:

//二叉树的销毁
void TreeDestroy(BTNode* root)
{if (root == NULL)//遇到空说明遍历结束,开始返回{return;}TreeDestroy(root->left);TreeDestroy(root->right);free(root);
}

三,链式二叉树的层序遍历

1,  层序遍历

        二叉树的层序遍历属于广度优先遍历(BFS),而二叉树的层序遍历其实就是对二叉树进行一层一层的遍历,完成二叉树的层序遍历需要用到我们之前学到的队列的知识。

        现在我们对队列进行一定的改装。以前我们所学到的队列中储存的为int类型的值,而这时候我们将int改变为二叉树节点的指针。经过以上操作之后,那么这个队列里所储存的就是二叉树节点的指针了。

typedef struct BinaryTreeNode* QDataType;
//二叉树的层序遍历
void TreeLevelOrder(BTNode* root);

         我们现在以以下的二叉树为例子:

        我们首先在队列中插入根节点

        然后我们取出根节点,并且同时在队列中加入其子节点2和4

        后我们取出节点2并且再次于队列中加入节点2对应的子节点(空则不进入队列)

        以此类推,直到当我们取出的节点为空时,那么此时对于这个二叉树的层序遍历就结束了。而在遍历的过程中,我们始终遵循一个思想,就是上层带下层。 所以在此思想的基础之上,我们便可以写出层序遍历的代码:

//二叉树的层序遍历
void TreeLevelOrder(BTNode* root)
{assert(root);//创建一个队列Queue q;//初始化队列QueueInit(&q);//向队列中插入第一个元素QueuePush(&q, root);while (!QueueEmpty(&q)){//取出队首节点并且打印BTNode* front = QueueFront(&q);QueuePop(&q);printf("%d ", front->val);//加入队首节点的非空子节点if (front->left){QueuePush(&q, front->left);}if (front->right){QueuePush(&q, front->right);}}
}

2,判断完全二叉树 

        当我们需要判断一棵树是否为完全二叉树时我们就可以使用该函数,它的返回值为布尔类型

//完全二叉树的判断
bool TreeComplete(BTNode* root);

        在写该函数时,我们则会用到以下的思路:

1,层序遍历这个二叉树,并且空指针也进入队列

2,取出队首节点遇到第一个空节点时,就开始进行判断,如果队列里面节点全为空就为完全二叉树,后面有非空节点就不为完全二叉树

        让我们结合下列的例子进行理解:

        我们以之前我们学到的层序遍历法来遍历这个二叉树,如下图所示:

        现在就是我们取出第一个空节点的时候,此时我们就对队列里的元素进行判断,如果队列里边全为空指针,那么这个二叉树就为完全二叉树,否则就不为完全二叉树。而由我们的例子可见,该二叉树就为完全二叉树。 

        所以我们就可以将此方法转变为如下的代码:

//完全二叉树的判断
bool TreeComplete(BTNode* root)
{assert(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;
}

四,结语

        其实到了这里,关于二叉树的基本知识我们就学完了。在二叉树中,递归知识就十分的重要,也比较烧脑,所以在学习这部分知识的时候我们就需要多画图进行理解

        后续我们也将慢慢进入排序的学习,一起加油吧!

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

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

相关文章

告别红色波浪线:tsconfig.json 配置详解

使用PC端的朋友,请将页面缩小到最小比例,阅读最佳! tsconfig.json 文件用于配置 TypeScript 项目的编译选项。如果配不对,就会在项目中显示一波又一波的红色波浪线,警告你这些地方的类型声明存在问题。 一般我们遇到这…

在没有dubbo-admin情况下如何判断zk中注册的dubbo服务是否注册成功

通常我们都是通过dubbo-admin来查看dubbo服务是否注册成功,那么如果没有部署dubbo-admind的情况下,我们如何来判断dubbo服务是否注册成功: 一、首先我们进入到zookeeper bin目录下使用以下指令连接到zk: ./zkCli.sh -server ip:port ip&…

Linux文件系统原理

Linux文件系统 冯诺依曼在1945年提出计算机的五大组成部分 运算器:CPU 控制器:CPU 存储器:内存和硬盘 输入设备:鼠标、硬盘 输出设备:显示器一、硬盘结构 机械硬盘结构 扇区:硬盘的最小存储单位&#xff…

Transformer讲解大纲,写PPT的可参考

前言 在这个信息如星辰般璀璨的时代,我们被无数的语言和文字包围。它们如同夜空中闪烁的繁星,每一颗都蕴藏着独特的故事和知识。然而,如何解读这些星辰的秘密,如何将它们的光芒汇聚成智慧的海洋,成为了我们这个时代的挑战。今天,我们将一起探索一种名为Transformer的神秘…

【路径规划】基于遗传算法GA实现最短距离 多起点多终点多旅行商问题求解附Matlab代码

基于遗传算法GA实现最短距离 多起点多终点多旅行商问题求解 研究背景:研究步骤:研究方法和技术路线:代码研究背景: 多起点多终点多旅行商问题是旅行商问题(TSP)的一个扩展,该问题要求确定多个旅行商从各自的起点出发,分别经过一系列目标点最终回到各自的终点,使得总路…

IOT技术怎么落地?以宝马,施耐德为例

物联网技术 物联网(IoT)技术正逐渐成为数字化工厂转型的核心驱动力。本文将通过实际案例,探讨IoT技术如何促进制造业的数字化转型,提高生产效率,降低成本,并提升产品质量。 1. 物联网技术简介 物联网技术通…

vue 模拟随机经纬度(小数点后保留6位),直接可用

1.随机生成经纬度 // 随机生成经纬度的方法function generateRandomLatLng(latitudeRange, longitudeRange) {const randomLat (Math.random() * latitudeRange.max latitudeRange.min).toFixed(6)const randomLng (Math.random() * longitudeRange.max longitudeRange.mi…

MySQL数据库基础:使用、架构、SQL语句、存储引擎

文章目录 什么是数据库CS模式 基本使用安装链接服务器服务器、数据库、表关系简单使用数据库在Linux下的体现 MySQL架构连接器层客户端层服务层存储引擎层物理存储层 SQL分类存储引擎 什么是数据库 mysql:数据库服务的客户端mysqld:数据库服务的服务器端…

PLC_博图系列☞R_TRIG:检测信号上升沿

PLC_博图系列☞R_TRIG:检测信号上升沿 文章目录 PLC_博图系列☞R_TRIG:检测信号上升沿背景介绍R_TRIG: 检测信号上升沿说明参数示例 关键字: PLC、 西门子、 博图、 Siemens 、 R_TRIG 背景介绍 这是一篇关于PLC编程的文章&a…

[ C++ ] 类和对象( 中 ) 2

目录 前置和后置重载 运算符重载和函数重载 流插入流提取的重载 全局函数访问类私有变量 友员 const成员 取地址及const取地址操作符重载 前置和后置重载 运算符重载和函数重载 流插入流提取的重载 重载成成员函数会出现顺序不同的情况(函数重载形参顺序必须相…

数据结构(五)树与二叉树

2024年5月26日一稿(王道P142) 基本概念 术语 性质 二叉树 5.2.2 二叉树存储结构

Spring从零开始学使用系列(三)--Spring框架中@Value注解和配置管理详解

如果各位老爷觉得可以,请点赞收藏评论,谢谢啦!! 文章中涉及到的图片均由AI生成 公众号在最下方!!! 目录 1. 如何在Spring中使用Value注解 1.1 基本用法 1.2提供默认值 2. 如何配置和使用Prop…

嵌入式进阶——数码管2

🎬 秋野酱:《个人主页》 🔥 个人专栏:《Java专栏》《Python专栏》 ⛺️心若有所向往,何惧道阻且长 文章目录 驱动封装封装的一些疑问数字走马灯实现扩展知识 驱动封装 根据前面的内容可以将代码进行封装,封装后作为一个独立的整…

贪心题目总结

1. 最长递增子序列 我们来看一下我们的贪心策略体现在哪里??? 我们来总结一下: 我们在考虑最长递增子序列的长度的时候,其实并不关心这个序列长什么样子,我们只是关心最后一个元素是谁。这样新来一个元素之后&#xf…

HTML5 Web组件技术应用

目录 Custom ElementsShadow DOMHTML TemplatesHTML ImportsHTML5 Web Components技术是一组相关标准和API的集合,旨在增强Web开发中的组件化能力,允许开发者创建可重用、封装良好的自定义UI组件,这些组件拥有独立的视图层(样式)、逻辑(行为)和结构(模板)。Web Compon…

【Week-R1】RNN实现心脏病预测,基于tensorflow框架

文章目录 一、什么是RNN?二、准备环境和数据2.1 导入数据 三、构建模型四、训练和预测五、其他(1)sklearn模块导入报错:ModuleNotFoundError: No module named sklearn(2)优化器改为SGD,accurac…

类和对象2

三、C对象模型和this指针 3.1 成员变量和成员函数分开存储 在C中&#xff0c;类内的成员变量和成员函数分开存储&#xff0c;只有非静态成员变量才属于类的对象上 #define _CRT_SECURE_NO_WARNINGS 1 #include <iostream> #include <string.h> using namespace …

Linux系统之GoAccess实时Web日志分析工具的基本使用

Linux系统之GoAccess实时Web日志分析工具的基本使用 一、GoAccess介绍1.1 GoAccess简介1.2 GoAccess功能1.3 Web日志格式 二、本地环境介绍2.1 本地环境规划2.2 本次实践介绍 三、检查本地环境3.1 检查本地操作系统版本3.2 检查系统内核版本3.3 检查系统镜像源3.4 更新软件列表…

JavaFX安装与使用

前言 最近学习了javafx,开始时在配置环境和导包时遇到了一些麻烦,关于网上很多方法都尝试过了,现在问题都解决了,和大家分享一下我是怎么实现javafx的配置,希望大家可以通过这个方法实现自己的环境配置! &#x1f648;个人主页: 心.c &#x1f525;文章专题:javafx &#x1f49…

如何在linux命令行(终端)执行ipynb 文件。可以不依赖jupyter

1.安装 runipy pip install runipy 2.终端运行 runipy <YourNotebookName>.ipynb 在终端命令行执行shell脚本&#xff0c;&#xff08;也可以在crontab 中执行&#xff09;&#xff1a; (base) [recommendapp-0-5-B-006 script]$ cat run1.sh #!/bin/bashcd /home/recom…