【数据结构】详解二叉树及其操作

无论你觉得自己多么的了不起,也永远有人比你更强。💓💓💓

目录

  ✨说在前面

🍋知识点一:二叉树的遍历

 • 🌰1.创建一棵二叉树

• 🌰2.二叉树的遍历

•🔥前序遍历

•🔥中序遍历

•🔥后序遍历

•🔥层序遍历

•🔥给出两种遍历如何求二叉树?

🍋知识点二:二叉树基本方法

 • 🌰1.二叉树节点个数

• 🌰2.二叉树叶子节点个数

• 🌰3.二叉树第k层节点个数

• 🌰4.二叉树的高度

• 🌰5.查找值为x的节点

• 🌰6.判断是否为完全二叉树

• 🌰7.二叉树的销毁

• ✨SumUp结语


  ✨说在前面

亲爱的读者们大家好!💖💖💖,我们又见面了,经过上一篇章中“堆”的学习,大家已经了解了树的基本结构。但是,堆只是树中特殊的一种数据结构,并且是基于树的一种数据结构。

今天我们将要学习它更加抽象的一面,即二叉树,那什么是二叉树,他们又是用什么来实现,又有什么作用呢?我们今天就详细剖析二叉树这一的数据结构吧~

在上一篇文章中,为了引入堆,也很清晰地介绍了树和二叉树的概念和性质,忘记的小伙伴可以去看看。

  👇👇👇
💘💘💘知识连线时刻(直接点击即可)

  🎉🎉🎉复习回顾🎉🎉🎉

         【数据结构】详解二叉树之堆

  博主主页传送门:愿天垂怜的博客

 

🍋知识点一:二叉树的遍历

 • 🌰1.创建一棵二叉树

在学习二叉树的基本操作之前,我们首先需要创建一棵二叉树,然后才能学习其相关的基本操作。但是由于现在我们知识有限,先手动创建一个二叉树一遍快速进入二叉树操作的学习,等以后我们再回过头来学习它真正的创建方式。

比如我们要创建如下的一棵二叉树:

代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>//创建二叉树的数据结构
typedef int BTDatatype;typedef struct BinaryTreeNode
{BTDatatype data;struct BinaryTreeNode* left;struct BinaryTreeNode* right;
}BTNode;//创建二叉树的节点
static BTNode* BuyNode(BTDatatype x)
{BTNode* node = (BTNode*)malloc(sizeof(BTNode));if (node == NULL){perror("malloc operation failed");exit(1);}node->data = x;node->left = node->right = NULL;return node;
}//手动创建二叉树
BTNode* CreateBinaryTree()
{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;return node1;
}
注意:上面的代码不是创建二叉树的真正的方式,真正的二叉树是递归定义的,因此后序基本操作

    

• 🌰2.二叉树的遍历

学习二叉树结构,最简单的方式就是遍历。所谓二叉树遍历1(Traversal)是按照某种特定的规则,依次堆二叉树中的节点进行相应的操作,使得每个节点只操作依次。访问节点所做的操作依赖与具体的应用问题。

遍历是二叉树上最重要的运算之一,也是二叉树上进行其他运算的基础。

按照规则,二叉树的遍历有:前序/中序/后序的递归结构遍历;

🔥前序遍历(Prevorder Traversal)——访问根节点的操作发生在遍历其左右子树之前。

🔥中序遍历(Inorder Traversal)——访问根节点的操作发生在遍历其左右子树之中(间)。

🔥后序遍历(Postorder Traversal)——访问根节点的操作发生在遍历其左右子树之后。

由于被访问的节点必然是某子树的根,所以N(Node)、L(Left subtree)、R(Right subtree)又可以解释为根、根的左子树和根的右子树。NLR、LNR和LRN分别又称为先跟遍历、中根遍历和后根遍历。 

•🔥前序遍历

对于以下这一棵二叉树:

我们如果按照访问根节点的操作发生在遍历其左右子树之前,那么所能得到这样的遍历顺序:1,2,3,4,5,6,这样的遍历称之为前序遍历。对于这样一棵树,我们先访问左子树,再访问根,最后访问右子树,而左子树和右子树中又是按照根——左子树——右子树的顺序遍历,以此往复,直到遍历完成。

代码如下:

void PrevOrder(BTNode* root)
{if (root == NULL){printf("N ");return;}printf("%d ", root->data);PrevOrder(root->left);PrevOrder(root->right);
}

上面代码完成了二叉树以前序遍历的方式打印每个节点中的值。.

•🔥中序遍历

对于以下这一棵二叉树:

我们如果按照访问根节点的操作发生在遍历其左右子树之中(间),那么所能得到这样的遍历顺序:3,2,1,5,4,6,这样的遍历称之为中序遍历。对于这样一棵树,我们先访问根,再访问左子树,最后访问右子树,而左子树和右子树中又是按照左子树——根——右子树的顺序遍历,以此往复,直到遍历完成。 

代码如下:

void InOrder(BTNode* root)
{if (root == NULL){printf("N ");return;}InOrder(root->left);printf("%d ", root->data);InOrder(root->right);
}

 

•🔥后序遍历

 对于以下这一棵二叉树:

我们如果按照访问根节点的操作发生在遍历其左右子树之后,那么所能得到这样的遍历顺序:3,2,5,6,4,1,这样的遍历称之为后序遍历。对于这样一棵树,我们先访问左子树,再访问右子树,最后访问根,而左子树和右子树中又是按照左子树——右子树——根的顺序遍历,以此往复,直到遍历完成。 

代码如下:

void PostOrder(BTNode* root)
{if (root == NULL){printf("N ");return;}PostOrder(root->left);PostOrder(root->right);printf("%d ", root->data);
}

 

•🔥层序遍历

除了先序遍历,中序遍历、后序遍历外,还可以对二叉树进行层序遍历,如下面这棵二叉树:

设二叉树的根节点 所在的层数为1,层序遍历就是从所在二叉树的根节点出发,首先访问第一层的树根节点,然后从左到右访问第二层上的节点,接着是第三层的节点,以此类推,自上而下,自左至右逐层访问树的节点的过程就是层序遍历。

实现二叉树的层序遍历,我们需要借助另一数据结构——队列。我们需要将根节点入队列,再每删除一个节点时,进行需要的操作,并将它的左右子节点入队列即可。

代码如下:

void TreeLevelOrder(BTNode* root)
{Queue q;QueueInit(&q);if (root)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题为例,假设我们要求整棵二叉树:

前序遍历:E F H I G J K        中序遍历:H F I E J K G

1.前序遍历确定根

即前序遍历的第一个E即为整棵二叉树的根节点。

2.中序遍历分割左右子树

即在中序遍历中找到E,则E左边的H F I为左子树,右边的J K G为右子树。

由此我们得到一下分析:

 分析到这个地方,我们可以得到左子树不为空,那么前序遍历的第二个F就是左子树的根,进而可以从中序遍历中找到F,它的左边H就是左子树的左子树,右边I就是左子树的右子树。同理,左子树走完后的第一个为G,则G是右子树的根节点,在中序遍历中找到G,它的左边J K就是右子树的左子树,而右子树的右子树为空。那么J K同理可以分析出J为它的左子树,K是J的右子树,即整棵树为:

🍋知识点二:二叉树基本方法

 • 🌰1.二叉树节点个数

我们利用递归的思想,将大问题拆分成许多类似的小问题。二叉树的节点个数等于它左子树的节点个数加上它右子树的节点个数再加1(它自己),而左子树和右子树的节点数同样等于它的左、右子树节点数和它自己,递归到最后节点如果为空,数量为0即可。

int TreeSize(BTNode* root)
{return root == NULL ? 0 : TreeSize(root->left) + TreeSize(root->right) + 1;
}

    

• 🌰2.二叉树叶子节点个数

我们的思想还是不变,都是递归的思想。叶子节点指的是度为0的节点,所以我们要检查一个节点它的左右孩子节点是否为空,如果都为空,那么就是叶子节点,返回1;如果不都为空,就递归下去,等于它的左子树和右子树的叶子节点之和;如果本身就是空,那就是0。

int TreeLeafSize(BTNode* root)
{if (root == NULL)return 0;return (!root->left && !root->right) ? 1 : TreeLeafSize(root->left) + TreeLeafSize(root->right);
}

    

• 🌰3.二叉树第k层节点个数

第k层的节点个数等于左右子树第k-1层的节点个数之和,同样递归下去,直到k=1,也就是第1层,那就直接返回1。特殊地,如果节点为空,返回0。

int TreeLevelKSize(BTNode* root, int k)
{if (root == NULL)return 0;if (k == 1)return 1;return TreeLeafSize(root->left, k - 1) + TreeLeafSize(root->right, k - 1);
}

    

• 🌰4.二叉树的高度

二叉树的高度等于它左右子树高的那一棵子树的高度+1,我们可以记录左右子树的值再进行判断,也可以用fmax函数,如果数为空,返回0。fmax函数在头文件math.h中声明。

int TreeHeight(BTNode* root)
{if (root == NULL)return 0;/*int left = TreeHeight(root->left);int right = TreeHeight(root->right);return left > right ? right + 1 : left + 1;*/return fmax(TreeHeight(root->left), TreeHeight(root->right)) + 1;
}

注意:这里使用三目运算符会使性能大大降低,因为每次return都要更多次地计算子树的高度。

    

• 🌰5.查找值为x的节点

用递归的方式遍历整棵树,如果为空就直接return NULL,不为空判断值是否为x,不为x就继续往下递归,为x就返回。也就是看自己是不是x,不是就返回左子树的查找,左子树也没有就返回右子树的查找。

BTNode* TreeFind(BTNode* root, int x)
{if (root == NULL)return NULL;if (root->data == x)return root;BTNode* ret = TreeFind(root->left, x);if (ret)return ret;return TreeFind(root->right, x);
}

    

• 🌰6.判断是否为完全二叉树

这时我们依然需要借助队列来进行判断。我们的思路是:将数中的每一个节点按照层序遍历的方式入队列(空节点也入队列),如果进入队列的节点为空,那么就停止,去判断后面所剩下的节点是否都为空。如果都为空,那就是完全二叉树,如果后面有不为空的节点,就不是完全二叉树。

 

代码如下:

bool TreeComplete(BTNode* root)
{Queue q;QueueInit(&q);if (root)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){QueueDestroy(&q);return false;}}QueueDestroy(&q);return true;
}

    

• 🌰7.二叉树的销毁

销毁儿茶俗话就比较简单了,依次释放左右节点再释放根1节点就可以了。

void TreeDestroy(BTNode* root)
{if (root == NULL)return;free(root->left);free(root->right);free(root);
}

• ✨SumUp结语

到这里本篇文章的内容就结束了,二叉树比我们以往的数据结构更加抽象,相信大家看完本篇文章已经发现了,涉及到的实现方法不断用到了递归的思想。希望大家可以好好复习今天的内容,自己尝试写代码~

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

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

相关文章

提升自身的国际影响力-香港服务器托管的优势

随着全球化的不断深入&#xff0c;中国企业正以前所未有的速度走向世界舞台&#xff0c;不仅在全球市场上展现其竞争力&#xff0c;更在寻求通过技术创新和服务优化来提升自身的国际影响力。在这一过程中&#xff0c;服务器的选择与托管成为了一个至关重要的环节。特别是在香港…

基于SpringBoot实现验证码功能

目录 一 实现思路 二 代码实现 三 代码汇总 现在的登录都需要输入验证码用来检测是否是真人登录&#xff0c;所以验证码功能在现在是非常普遍的&#xff0c;那么接下来我们就基于springboot来实现验证码功能。 一 实现思路 今天我们介绍的是两种主流的验证码&#xff0c;一…

使用php adodb5连接人大金仓数据库

打开php中的pgsql扩展 extensionpgsql使用adodb5连接kingbase数据库 <?php include("adodb5/adodb.inc.php"); $fox_dbtype pgsql; $fox_host 192.168.1.66; $fox_user system; $fox_pwd 123456; $fox_dbname testkingbase; $fox_port 54321;$dbNewADOCo…

大数据技术--实验01-Hadoop的安装与使用【实测可行】

使用下面表中的软件版本进行配置&#xff1a; 准备好后&#xff0c;按照下面的步骤进行配置。 配置VMware网络 在VMWare主界面&#xff0c;点击“编辑”>“虚拟网络编辑”菜单进入虚拟网卡参数设置界面。选择VMnet8条目&#xff0c;点击“NAT设置”按钮后可以看到我们的VM…

使用UDP套接字编程详解【C语言】

UDP&#xff08;User Datagram Protocol&#xff0c;用户数据报协议&#xff09;是一种面向无连接的传输层协议&#xff0c;用于在计算机网络上发送数据。它与 TCP&#xff08;Transmission Control Protocol&#xff0c;传输控制协议&#xff09;相比具有轻量、高效的特点&…

英语(二)-我的学习方式

章节章节汇总我的学习方式历年真题作文&范文 目录 1、背单词 2、学语法 3、做真题 4、胶囊助学计划 写在最前&#xff1a;我是零基础&#xff0c;初二就听天书的那种。 本专栏持续更新学习资料 1、背单词 单词是基础&#xff0c;一定要背单词&#xff01;考纲要求要…

云动态摘要 2024-07-23

给您带来云厂商的最新动态,最新产品资讯和最新优惠更新。 最新优惠与活动 数据库上云优选 阿里云 2024-07-04 RDS、PolarDB、Redis、MongoDB 全系产品新用户低至首年6折起! [免费体验]智能助手ChatBI上线 腾讯云 2024-07-02 基于混元大模型打造,可通过对话方式生成可视化…

中文分词库 jieba 详细使用方法与案例演示

1 前言 jieba 是一个非常流行的中文分词库&#xff0c;具有高效、准确分词的效果。 它支持3种分词模式&#xff1a; 精确模式全模式搜索引擎模式 jieba0.42.1测试环境&#xff1a;python3.10.9 2 三种模式 2.1 精确模式 适应场景&#xff1a;文本分析。 功能&#xff1…

【Zotero插件】Zotero Tag为文献设置阅读状态 win11下相关设置

【Zotero插件设置】Zotero Tag为文献设置阅读状态 win11下相关设置 1.安装Zotero Tag1.1安装1.2配置1.3 win11的相关设置1.3.1 字体安装 参考教程 2.支持排序的标注参考教程 1.安装Zotero Tag 1.1安装 Zotero Tag插件下载链接安装方法&#xff1a;Zotero–》工具–》附加组件…

googleTest 源码主线框架性分析——TDD 01

TDD&#xff0c;测试驱动开发&#xff0c;英文全称Test-Driven Development&#xff0c;简称TDD&#xff0c;是一种不同于传统软件开发流程的新型的开发方法。它要求在编写某个功能的代码之前先编写测试代码&#xff0c;然后只编写使测试通过的功能代码&#xff0c;通过测试来推…

苹果和乔布斯的传奇故事,从车库创业到万亿市值巨头

苹果公司的品牌故事&#xff0c;就像一部充满创新、挑战与辉煌的科幻大片&#xff0c;让人目不暇接。 故事始于1976年&#xff0c;那时&#xff0c;年轻的史蒂夫乔布斯与斯蒂夫沃兹尼亚克在加州的一个简陋车库里&#xff0c;用他们的热情和智慧&#xff0c;捣鼓出了世界上第一…

python学习之闭包与装饰器

一、闭包 闭包允许一个函数访问并操作函数外部的变量&#xff08;即父级作用域中的变量&#xff09;&#xff0c;即使在该函数外部执行。 特性&#xff1a; (1)外部函数嵌套内部函数。 (2)外部函数可以返回内部函数。 (3)内部函数可以访问外部函数的局部变量。 def out()…

linux中使用docker安装mongodb

随着容器的普及&#xff0c;越来越多服务都喜欢跑在容器中&#xff0c;并且安装也很方便快捷&#xff0c;接下来一起看下linux中使用docker来安装mongodb吧&#xff01; 1.首先安装docker&#xff1b; 使用Yum 进行安装&#xff0c;我安装docker比较喜欢参考阿里云中的安装步骤…

通过泛型+函数式编程封装成通用解决方案|缓存穿透、缓存击穿,缓存雪崩

缓存更新方法封装 用到了泛型、函数式编程。 使用函数式编程是因为我们这个是一个通用的工具&#xff0c;使用泛型&#xff08;泛型&#xff08;Generics&#xff09; 允许我们定义类、接口和方法&#xff0c;可以使用不同类型的参数进行操作&#xff09;可以实现数据类型的通…

Mem0 - 个人 AI 的内存层

文章目录 一、关于 Mem0核心功能&#x1f511;路线图 &#x1f5fa;️常见用例Mem0与RAG有何不同&#xff1f; 二、快速入门 &#x1f680;1、安装2、基本用法&#xff08;开源&#xff09;3、高级用法&#x1f527;4、大模型支持 三、MultiOn1、概览2、设置和配置4、将记忆添加…

鸿蒙仓颉语言【模块module】

module 模块 模块配置文件&#xff0c;这里指项目的modules.json 文件&#xff0c;用于描述代码项目的基础元属性。 {"name": "file name", //当前项目的名称"description": "项目描述", //项目描述"version": "1.0…

视频汇聚平台EasyCVR启动出现报错“cannot open shared object file”的原因排查与解决

安防视频监控EasyCVR安防监控视频系统采用先进的网络传输技术&#xff0c;支持高清视频的接入和传输&#xff0c;能够满足大规模、高并发的远程监控需求。EasyCVR平台支持多种视频流的外部分发&#xff0c;如RTMP、RTSP、HTTP-FLV、WebSocket-FLV、HLS、WebRTC、fmp4等&#xf…

kafka基础介绍

一、为什么使用消息队列 1.使用同步的通信方式来解决多个服务之间的通信 同步的通信方式会存在性能和稳定性的问题。 2.使用异步的通信方式 针对于同步的通信方式来说,异步的方式,可以让上游快速成功,极大提高了系统的吞吐量。而且在分布式系统中,通过下游多个服务的 分布式事…

怎么拼接几张图片为一张?拼接几张图片为一张的四种方法推荐

怎么拼接几张图片为一张&#xff1f;要将几张图片拼接成一张完整的图像&#xff0c;你可以利用现代软件和工具来实现这一操作。这种技术可以帮助你创建更大、更复杂的图像&#xff0c;无论是为了美学上的需要还是为了展示更完整的视觉信息。通过合并多张图片&#xff0c;你可以…

Windows上让Qt支持https请求

一.前言 Qt默认其实支持https的&#xff0c;但需要openssl的支持。所以有时候你代码中写了支持https的请求连接&#xff0c;发现程序可以运行&#xff0c;但到了https请求时会报错&#xff0c;如下&#xff1a; 这就是没有openssl的支持&#xff0c;导致QSslSocket无法进行ht…