【数据结构】二叉树的模拟实现

前言:前面我们学习了堆的模拟实现,今天我们来进一步学习二叉树,当然了内容肯定是越来越难的,各位我们一起努力!

💖 博主CSDN主页:卫卫卫的个人主页 💞
👉 专栏分类:数据结构 👈
💯代码仓库:卫卫周大胖的学习日记💫
💪关注博主和博主一起学习!一起努力!
在这里插入图片描述


什么是树

树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。光看文字可能大家理解不了,我们看下图。在这里插入图片描述


树的相关概念

  1. 节点的度:一个节点含有的子树的个数称为该节点的度。 如上图:A的为3。
  2. 叶节点或终端节点:度为0的节点称为叶节点; 如上图:F、G、H、I…等节点为叶节点。
  3. 非终端节点或分支节点:度不为0的节点; 如上图:B、C、D…等节点为分支节点.
  4. 双亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点。 如上图:A是B的父节点。
  5. 孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点。 如上图:B是A的孩子节点。
  6. 兄弟节点:具有相同父节点的节点互称为兄弟节点。 如上图:B、C是兄弟节点。
  7. 树的度:一棵树中,最大的节点的度称为树的度。 如上图:树的度为5。
  8. 节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推。
  9. 树的高度或深度:树中节点的最大层次。如上图:树的高度为4。
  10. 堂兄弟节点:双亲在同一层的节点互为堂兄弟。如上图:G、G互为兄弟节点 。
  11. 节点的祖先:从根到该节点所经分支上的所有节点。如上图:A是所有节点的祖先。
  12. 子孙:以某节点为根的子树中任一节点都称为该节点的子孙。如上图:所有节点都是A的子孙。
  13. 森林:由m(m>0)棵互不相交的树的集合称为森林。

什么是二叉树

二叉树是一种常见的树型数据结构,由若干个节点组成,每个节点最多有两个子节点,分别称为左子节点和右子节点。二叉树有以下特点(如图所示):

  1. 每个节点最多有两个子节点,且子节点的位置是固定的。
  2. 左子节点在父节点的左边,右子节点在父节点的右边。
  3. 二叉树的子树也是二叉树。
  4. 二叉树的每个节点都有一个值,可以是任意类型的数据。
    在这里插入图片描述

讲完了二叉树的基本性质,我们开始模拟实现二叉树吧!

二叉树的基本功能

  1. 前序遍历–根、左、右 – 深度优先遍历
  2. 计算树节点。
  3. 中序遍历–左、根、右
  4. 计算叶子节点
  5. 树的高度
  6. 求第k层结点的个数
  7. 二叉树查找值为x的结点
  8. 通过前序遍历的数组构建二叉树。
  9. 销毁二叉树
  10. 层序遍历 – 广度优先遍历
  11. 判断二叉树是否是完全二叉树

二叉树的初始化(手搓二叉树)

思路导读:由于通过数组创建二叉树比较难,我们先放在博客的后面在去讲解,但是我们又需要二叉树怎么办,那就手搓一个。
代码演示:

typedef int BTDataType;typedef struct BinaryTreeNode
{BTDataType data;struct BinaryTreeNode* left;struct BinaryTreeNode* right;}TreeNode;TreeNode* BuyNode(int x)
{TreeNode* node = (TreeNode*)malloc(sizeof(TreeNode));assert(node);node->data = x;node->left = NULL;node->right = NULL;return node;
}TreeNode* CreateTree()//创建二叉树
{TreeNode* node1 = BuyNode(1);TreeNode* node2 = BuyNode(30);TreeNode* node3 = BuyNode(2);TreeNode* node4 = BuyNode(71);TreeNode* node5 = BuyNode(99);TreeNode* node6 = BuyNode(11);TreeNode* node7 = BuyNode(21);node1->left = node2;node1->right = node3;node2->left = node4;node2->right = node5;node3->right = node7;node3->left = node6;return node1;
}

创建好后的树的结构如下图所示
在这里插入图片描述


二叉树的前序遍历

思路导读:二叉树的前序遍历指先遍历二叉树的根,左子树,在遍历右子树。我们可以先画个图来模拟一下它遍历的顺序如下图:
在这里插入图片描述
既然图都画出来了,我们肯定思路都大致清晰了,那用什么方式去遍历呢?循环还是什么?今天我们要讲的方式是递归解决二叉树的遍历,这种是目前比较简单的方式。
代码实现:对于刚刚接触二叉树的友友们可能还不能理解这个递归的方式,没事可以看下图的递归展开图帮助你进一步理解

void PrevOrder(TreeNode* root)//前序遍历--根、左、右 -- 深度优先遍历
{if (root == NULL){return NULL;}printf("%d ", root->data);PrevOrder(root->left);PrevOrder(root->right);
}

在这里插入图片描述
运行结果:在这里插入图片描述


二叉树的中序遍历

思路导读:二叉树的中序遍历指先遍历左子树,再遍历根,最后遍历右子树。这个就不为大家再画递归展开图了,看代码!
代码演示

void InOrder(TreeNode* root)//中序遍历--左、根、右
{if (root == NULL){return NULL;}InOrder(root->left);printf("%d ", root->data);InOrder(root->right);
}

运行结果在这里插入图片描述


计算树节点

思路导读:我们要想计算树节点的个数,我们只需要将其左子树,右子树,还有根的值全部算出有多少即可,我们只需通过像前序遍历的思想来计算。如果不懂的话可以看下面的递归展开图(博主就画了前几步)。在这里插入图片描述
代码实现:

int TreeSize(TreeNode* root)//计算树节点
{if (root == NULL){return 0;}return TreeSize(root->right) + TreeSize(root->left) + 1;}

计算叶子节点

思路导读:我们可以通过遍历该树的左子树和右子树,如果左子树和右子树同时为NULL我们就让它返回1,以此类推,我们可以通过像前面一样的方式遍历二叉树达到计算叶子节点的效果(部分递归展开图)。
在这里插入图片描述
代码实现:

int TreeLeafSize(TreeNode* root)//计算叶子节点
{if (root == NULL){return 0;}if((root->left)== NULL &&(root->right) == NULL){return 1;}return TreeLeafSize(root->left) + TreeLeafSize(root->right);
}

计算树的高度

思路导读:计算树的高度我们可以通过比较它的左子树和右子树找出其树高度中较大的那个然后加上一即可算出来这个树的高度,怎么比较呢?那我们就可以通过利用fmax这个函数来比较其找到最大值。
(部分递归展开图如下)在这里插入图片描述
代码实现:

int TreeHight(TreeNode* root)//树给高度
{if (root == NULL){return 0;}//fmax函数,它是C语言(C99)中的一个内置函数,用于比较两个浮点数的大小并返回较大值。return fmax(TreeHight(root->left), TreeHight(root->right)) + 1;
}

求第k层节点的个数

思路导读:要想求第k层节点的个数我们需要将该层中左子树和右子树的位置分别记录下来,然后将其相加就是该层的个数。那么另一个问题来了,我们如何找到是这一层呢?我们可以通过每让它往下走一层时,就让k-1,依次递归,当k完全等于1的时候说明已经到了这一层,再返回1即可。(递归展开图如下)
在这里插入图片描述
代码实现:

int TreeLevelK(TreeNode* root, int k)//求第k层结点的个数
{assert(k > 0);if (root == NULL)return 0;if (k == 1)return 1;

二叉树查找值为x的节点

思路导读:我们通过前面写了这么多的二叉树的接口,这里我们可以想到,我们先查左子树中是否有相同的,然后我们再去查看右子树中是否有相同的,如果左子树找到了就将该值返回即可。(部分递归展开图如下)
在这里插入图片描述
代码实现:

TreeNode* TreeFind(TreeNode* root, BTDataType x)//二叉树查找值为x的结点
{if (root == NULL){return NULL;}if (root->data == x){return root;} TreeNode* cur = TreeFind(root->left,x);//先找左子树,通过指针记录,防止找到了接着往下走if (cur){return cur;}TreeNode* cur1 = TreeFind(root->right, x);//再找右子树,同理指针记录if (cur1){return cur1;}return NULL;
}

通过前序遍历的数组构建二叉树

我们先假定传来的数组为:char arr[] = { 'A','B','D','#','#','E','#','H','#','#','C','F','#','#','G','#','#' };
该’#'代表NULL,因此我们先画出其前序遍历的展开图(如下):
在这里插入图片描述
然后我们只需要像前序遍历一样,将其按照根、左子树、右子树一样依次开辟结点赋值,此时我们要注意的是一定要使用指针,防止在递归的过程中其值不会变化。
代码实现如下:

TreeNode* TreeCreate2(char* a, int* pi)//通过前序遍历的数组构建二叉树
{if (*(a + *pi) =='#'){(*pi)++;return NULL ;}TreeNode* root = (TreeNode*)malloc(sizeof(TreeNode));root->data = *(a + (*pi)++);root->left = TreeCreate2(a, pi);root->right = TreeCreate2(a, pi);return root;
}

运行结果:
在这里插入图片描述


二叉树的销毁

思路导读:二叉树的销毁可以像二叉树的后序遍历一样先左子树、右子树
代码实现:

void DestoryTree(TreeNode* root)//销毁
{if (root == NULL){return NULL;}DestoryTree(root->left);DestoryTree(root->right);free(root);}

层序遍历 – 广度优先遍历

思路导读:我们知道二叉树的一个特性,每一个节点都有俩个子节点,因此我们在可以充分的利用这个特性来实现我们层序遍历,我们可以模拟实现一个队列,让二叉树的根先存进去队列,然后打印其数据,就打印了第一层的数据,此时我们要打印第二层的数据,我们只需要将根的左子树和右子树在分别存入队列,在第二次循环中在依次打印即可实现层序遍历了。记住一定在队列中存的是二叉树根的地址而不是值(如下图所示)在这里插入图片描述
代码实现:

void levelorder(TreeNode* root)//层序遍历 -- 广度优先遍历
{QNode q1;QueueInit(&q1);// TreeHight(root);if (root){//QueuePush(Queue * pq, QDataType x)QueuePush(&q1, root);}int level = 1;while (!QueueEmpty(&q1)){while (level--){TreeNode* front = QueueFront(&q1);//将头元素地址保存在二叉树中QueuePop(&q1);printf("%c ", front->data);if (front->left){QueuePush(&q1, front->left);}if (front->right){QueuePush(&q1, front->right);}}level = QueueSize(&q1);printf("\n");}QueueDestroy(&q1);

测试函数:

void Test1()
{TreeNode* root = CreateTree1();char arr[] = { 'A','B','D','#','#','E','#','H','#','#','C','F','#','#','G','#','#' };int i = 0;levelorder(TreeCreate2(arr, &i));
}

运行结果:在这里插入图片描述


判断是否是完全二叉树

思路导读:我们可以像前面一样,让它们依次层次入队列,如果在入队列的过程中就碰到了NULL,就说明其不是完全二叉树,然后再将最后一层中队列的元素依次出队列,如果出队列的途中也碰到了NULL也就说明其不是完全二叉树。如果都没碰到则是完全二叉树。
在这里插入图片描述
代码实现

bool TreeComplete(TreeNode* root)
{Queue q;QueueInit(&q);if (root)QueuePush(&q, root);while (!QueueEmpty(&q)){TreeNode* front = QueueFront(&q);QueuePop(&q);if (front == NULL)break;QueuePush(&q, front->left);QueuePush(&q, front->right);}// 前面遇到空以后,后面还有非空就不是完全二叉树while (!QueueEmpty(&q)){TreeNode* front = QueueFront(&q);QueuePop(&q);if (front){QueueDestroy(&q);return false;}}QueueDestroy(&q);return true;
}

结语:今天的内容就到这里吧,谢谢各位的观看,如果有讲的不好的地方也请各位多多指出,作者每一条评论都会读的,谢谢各位。


🫵🫵🫵 祝各位接下来好运连连 💞

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

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

相关文章

CogVLM与CogAgent:开源视觉语言模型的新里程碑

引言 随着机器学习的快速发展,视觉语言模型(VLM)的研究取得了显著的进步。今天,我们很高兴介绍两款强大的开源视觉语言模型:CogVLM和CogAgent。这两款模型在图像理解和多轮对话等领域表现出色,为人工智能的…

A01、关于JVM的GC回收

引用类型 对象引用类型分为强引用、软引用、弱引用,具体差别详见下文描述: 强引用:就是我们一般声明对象是时虚拟机生成的引用,强引用环境下,垃圾回收时需要严格判断当前对象是否被强引用,如果被强引用&am…

GaN图腾柱无桥 Boost PFC(单相)九-EMI 滤波器容性电流影响分析

前言 为了防止 PFC 变换器中高频开关谐波对电网产生影响,同时抑制电网中的高频干扰对变换器运行的影响,一般通过在 PFC 变换器与交流电源之间加入EMI 滤波器消除共模干扰和差模干扰,使变换器满足相应的 EMI 标准。在基于GaN 功率器件的图腾柱…

GD32F4中断向量查询

中断向量表 中断向量对应函数 __Vectors DCD __initial_sp ; Top of StackDCD Reset_Handler ; Reset HandlerDCD NMI_Handler ; NMI HandlerDCD HardFault_Handler ;…

管理类联考——数学——真题篇——按题型分类——充分性判断题——蒙猜C

老规矩,先看目录,平均每个3-4C(C是月饼,月饼一般分为4块) C是什么,是两个都不行了,但联合起来可以,联合的英文是combined,好的,我知道这个英文也记不住&#…

【Python】管理项目第三方包

我们在开发python项目时,如果代码每移植到到其他机器上,就手动 pip install XXX 安装一次,这样手动介入 是不是不太方便? 那么,python有像java一样的maven管理包的工具吗?只需要一个类似pom的文件&#xff…

Excel怎样统计一列中不同的数据分别有多少个?

文章目录 1.打开Excel数据表2.选择“插入”,“数据透视表”3.选择数据透视表放置位置4.将统计列分别拖到“行”和“数值”区间5.统计出一列中不同的数据分别有多少个 1.打开Excel数据表 2.选择“插入”,“数据透视表” 3.选择数据透视表放置位置 4.将统计…

c 实现jpeg中的ALI(可变长度整数转换)正反向转换

用于DC的ALI表:DIFF 就是前后两个8X8块DC的差值,ssss就是DIFF值用二进制表示的位数 亮度,与色度的DC都是这种处理的。两个相邻的亮度与亮度比差,色度与色度比差产生DIFF, 扫描开始DIFF等于0。 用于AC ALI表:表中的AC…

喜讯!聚铭网络入选国家信息安全漏洞库(CNNVD)技术支撑单位

近日,国家信息安全漏洞库(CNNVD)公示2023年度新增技术支撑单位名单。经考核评定,聚铭网络正式入选并被授予《国家信息安全漏洞库(CNNVD)三级技术支撑单位证书》。 国家信息安全漏洞库(CNNVD&am…

解决腾讯云CentOS 6硬盘空间不足问题:从快照到数据迁移

引言: 随着数据的不断增加,服务器硬盘空间不足变成了许多运维人员必须面对的问题。此主机运行了httpd(apache服务),提供对外web访问服务,web资源挂载在**/data/wwwroot目录下,http日志存放在/data/wwwlogs目录下&…

11 v-bind指令

概述 v-bind指令可以说是Vue3中最常用的指令之一,使用v-bind,我们几乎能够给任何实现动态的绑定比值。 这里,我们主要演示以下,通过v-bind动态绑定CSS样式。 基本用法 我们创建src/components/Demo11.vue,在这个组…

JS逆向实战——开发者工具检测

说明:仅供学习使用,请勿用于非法用途,若有侵权,请联系博主删除 作者:zhu6201976 一、背景 在JS逆向领域,Chrome开发者工具是核心,抓包、调试、看调用栈等都离不开它。可以说,逆向人…

C语言--字符函数与字符串函数

大家好,我是残念,希望在你看完之后,能对你有所帮助,有什么不足请指正!共同学习交流 本文由:残念ing 原创CSDN首发,如需要转载请通知 个人主页:残念ing-CSDN博客,欢迎各位…

Java 数据结构篇-实现二叉搜索树的核心方法

🔥博客主页: 【小扳_-CSDN博客】 ❤感谢大家点赞👍收藏⭐评论✍ 文章目录 1.0 二叉搜索树的概述 2.0 二叉搜索树的成员变量及其构造方法 3.0 实现二叉树的核心接口 3.1 实现二叉搜索树 - 获取值 get(int key) 3.2 实现二叉搜索树 - 获取最小…

程序流程图的意义(合集)

程序流程图的意义 1、矩形 作用:一般用作要执行的处理(process),在程序流程图中做执行框。 在axure中如果是画页面框架图,那么也可以指代一个页面。有时候我们会把页面和执行命令放在同一个流程中做说明,这个时候将两类不同的矩形…

算法(2)——滑动窗口

前言: 步骤及算法模板: 确定两个指针变量,left0,right0; 进窗口: 判断: 出窗口 更新结果 接下来我们的所用滑动窗口解决问题都需要以上几个步骤。 一、长度最小的子数组 209. 长度最小的子数组 - 力扣(L…

Ebullient第一阶段开发小结

一. 简介 距离Ebullient硬件发布已有一段时间,小一个月吧,在这段时间内在努力的编写代码,现在终于完成了第一阶段的功能设计,算是一个小型的样机吧,基本的代码框架基本确定了,相信后续的会快一点(希望如此…

Nodejs 第二十六章(反向代理)

什么是反向代理? 反向代理(Reverse Proxy)是一种网络通信模式,它充当服务器和客户端之间的中介,将客户端的请求转发到一个或多个后端服务器,并将后端服务器的响应返回给客户端。 负载均衡:反向代理可以根…

二、W5100S/W5500+RP2040之MicroPython开发<DHCP示例>

文章目录 1 前言2 相关网络信息2 .1 简介2.2 DHCP工作原理2.3 DHCP的优点2.4 应用场景 3 WIZnet以太网芯片4 DHCP网络设置示例概述以及使用4.1 流程图4.2 准备工作核心4.3 连接方式4.4 主要代码概述4.5 结果演示 5 注意事项6 相关链接 1 前言 在这个智能硬件和物联网时代&#…

在Python中使用Kafka帮助我们处理数据

Kafka是一个分布式的流数据平台,它可以快速地处理大量的实时数据。Python是一种广泛使用的编程语言,它具有易学易用、高效、灵活等特点。在Python中使用Kafka可以帮助我们更好地处理大量的数据。本文将介绍如何在Python中使用Kafka简单案例。 一、安装K…