数据结构——二叉树的实现

文章目录

  • 一、二叉树概念的回顾
  • 二、二叉树结构的定义
  • 三、二叉树的创建
    • 方法一、写个创建结点的函数然后手动链接起来
      • 创建结点的函数
      • 手动链接
    • 方法二、通过前序遍历的数组的方式构建二叉树
      • 创建的函数声明
      • 创建函数的定义
  • 四、 二叉树的遍历
    • 前序遍历
    • 中序遍历
    • 后序遍历
    • 层序遍历
  • 五、二叉树的其他功能
    • 二叉树的销毁
    • 树的结点个数
    • 树的叶子结点个数
    • 第K层结点的个数
    • 树的高度
    • 查找值为k的结点
    • 判断是否是完全二叉树

一、二叉树概念的回顾

一棵二叉树是结点的一个有限集合,该集合:

  1. 或者为空
  2. 由一个根节点加上两棵别称为左子树和右子树的二叉树组成

在这里插入图片描述
注意:
1. 二叉树不存在度大于2的结点
2. 二叉树的子树有左右之分,次序不能颠倒,因此二叉树是有序树

从概念中可以看出,二叉树定义是递归式的,因此后序基本操作中基本都是按照该概念实现的

二、二叉树结构的定义

前面我们提到了二叉树的结构既可以用二叉链也可以用三叉链,下面我们基于二叉链来实现二叉树

//定义二叉树结构
typedef int BTDataType;
typedef struct BinaryTree
{BTDataType val;//储存数据struct BinaryTree* left;//左孩子结点struct BinaryTree* right;//右孩子结点
}BTNode;

三、二叉树的创建

方法一、写个创建结点的函数然后手动链接起来

创建结点的函数

//创建结点
BTNode* BuyNode(BTDataType x)
{BTNode* root = (BTNode*)malloc(sizeof(BTNode));if (root == NULL){perror("malloc fail");exit(1);}root->val = x;root->left = NULL;root->right = NULL;return root;
}

手动链接

//手动创建一个二叉树
BTNode* CreateNode()
{BTNode* n1 = BuyNode(1);BTNode* n2 = BuyNode(2);BTNode* n3 = BuyNode(3);BTNode* n4 = BuyNode(4);BTNode* n5 = BuyNode(5);BTNode* n6 = BuyNode(6);//BTNode* n7 = BuyNode(6);n1->left = n2;n1->right = n4;n2->left = n3;//n2->right = n7;n4->left = n5;n4->right = n6;return n1;
}

这样我们就成功创建了一个二叉树,如图

在这里插入图片描述

方法二、通过前序遍历的数组的方式构建二叉树

这里我们先来了解一下二叉树的遍历
按照规则,二叉树的遍历有:前序/中序/后序的递归结构遍历:

  1. 前序遍历(Preorder Traversal 亦称先序遍历)——访问根结点的操作发生在遍历其左右子树之前。
  2. 中序遍历(Inorder Traversal)——访问根结点的操作发生在遍历其左右子树之中(间)。
  3. 后序遍历(Postorder Traversal)——访问根结点的操作发生在遍历其左右子树之后。

例如在下面的数组的基础上利用前序遍历的思想创建二叉树
在这里插入图片描述

创建的函数声明

BTNode* BTCreateNode(BTDataType* a,int n,int* pi);

1.根据前序遍历的思想,我们发现数组的首元素就是根,通过递归本函数,我们可以先将根创建完后再创建左子树,然后创建右子树

2.遇到 # 就将此位置置为空指针然后退出递归,回到上一级

3.创建二叉树的其实就是一个堆逻辑的数组,为了改变下标,我们需要一个能够在递归时确定当前元素下标的变量,因此我们可以传地址,这样即使在递归途中,元素的下标就可以不断改变

创建函数的定义

BTNode* BTCreateNode(BTDataType* a,int n,int* pi)
{if ((*pi) >= n || a[(*pi)] == '#'){(*pi)++;//不能在外面++,因为如果不是"#",就不能跳过此位置return NULL;}BTNode* node = (BTNode*)malloc(sizeof(BTNode));if (node == NULL){perror("malloc fail");exit(1);}node->val = a[(*pi)++];node->left = BTCreateNode(a, n, pi);node->right = BTCreateNode(a, n, pi);return node;
}

四、 二叉树的遍历

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

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

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

注意:前序遍历、中序遍历、后序遍历普遍用的是递归的思想

前序遍历

前序遍历(Preorder Traversal 亦称先序遍历)——访问根结点的操作发生在遍历其左右子树之前。

其实访问顺序就是根 -> 左 ->右

下面画下前序遍历的递归过程
在这里插入图片描述
代码实现

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

中序遍历

中序遍历(Inorder Traversal) ——访问根结点的操作发生在遍历其左右子树之中(间)。
其实访问顺序就是左 -> 根 ->右

与前序遍历的思想差不多,就是访问顺序改变了一下

在这里插入图片描述

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

后序遍历

后序遍历(Postorder Traversal) ——访问根结点的操作发生在遍历其左右子树之后。
其实访问顺序就是左 -> 右 ->根
与前序遍历的思想类似,改变访问顺序即可

在这里插入图片描述

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

层序遍历

层序遍历:除了先序遍历、中序遍历、后序遍历外,还可以对二叉树进行层序遍历。 设二叉树的根节点所在层数为1,层序遍历就是从所在二叉树的根节点出发, 首先访问第一层的树根节点,然后从左到右访问第2层上的节点,接着是第三层的节点,以此类推,自上而下,自左至右逐层访问树的结点的过程就是层序遍历

层序遍历可以借助队列的结构来实现

在这里插入图片描述

//层序遍历 -> 借助队列
void LevelOrder(BTNode* root)
{Queue q;QueueInit(&q);if (root){QueuePush(&q, root);}while (!QueueEmpty(&q)){BTNode* node = QueueFront(&q);QueuePop(&q);printf("%d ", node->val);if (node->left){QueuePush(&q, node->left);}if (node->right){QueuePush(&q, node->right);}}QueueDestroy(&q);
}

五、二叉树的其他功能

二叉树的销毁

首先我们容易想到从根结点开始以次销毁,但是如果先将根结点销毁,那么就找不到左、右孩子了, 故我们可以反过来想, 先销毁左、右孩子,然后再销毁根结点

//二叉树的销毁
void BTDestroy(BTNode* root)
{if (root == NULL){return;}//利用后序遍历的思想销毁二叉树BTDestroy(root->left);BTDestroy(root->right);free(root);
}

树的结点个数

采用递归的思想,有结点就去访问左、右孩子,没有结点就返回0

//树的结点个数
int BTSize(BTNode* root)
{return root == NULL ? 0 : BTSize(root->left) + BTSize(root->right) + 1;
}

树的叶子结点个数

在寻找树的结点个数的前提下,加个判断,只要左、右孩子同时为空,才能返回 1

//树的叶子结点个数
int BTLeaveSize(BTNode* root)
{if (root == NULL){return 0;}if (root->left == NULL && root->right == NULL){return 1;}return BTLeaveSize(root->left) + BTLeaveSize(root->right);
}

第K层结点的个数

容易想到第一层的结点个数为1个,第二层的结点个数是第一层的左、右孩子不为空的个数,以此类推,第K层结点个数是第K - 1 层的孩子结点存在个数

//第K层结点的个数
int BTKSize(BTNode* root,int k)
{if (root == 0){return 0;}if (k == 1){return 1;}return BTKSize(root->left, k - 1) + BTKSize(root->right, k - 1);
}

树的高度

树的高度就是该树的深度,即左、右孩子之中的深度最大的那一个
注意: 应该将左、右孩子的深度给保存,减少递归的次数

//树的高度
int BTHeight(BTNode* root)
{if (root == NULL){return 0;}if (root->left == NULL && root->right == NULL){return 1;}int leftHeight = BTHeight(root->left);int rightHeight = BTHeight(root->right);return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
}

查找值为k的结点

根结点找到就返回,否则在左、右孩子之间查找
注意: 如果在左孩子里面找到了,就可以直接返回了,没有必要再在右孩子里面查找

//查找值为K的结点
BTNode* BTFind(BTNode* root, BTDataType k)
{if (root == NULL){return NULL;}if (root->val == k){return root;}BTNode* leftFind = BTFind(root->left, k);if (leftFind){return leftFind;}BTNode* rightFind = BTFind(root->right, k);if (rightFind){return rightFind;}return NULL;
}

判断是否是完全二叉树

其实就是在层序遍历的基础上,不管左、右孩子是不是为空,直接插入到队列里面

如果遇见第一个非空,就跳出循环,遍历此时的队列里面是否还有非空结点,没有就是完全二叉树,有就不是完全二叉树

//判断是否是完全二叉树
int CompleteTree(BTNode* root)
{Queue q;QueueInit(&q);if (root){QueuePush(&q, root);}while (!QueueEmpty(&q)){BTNode* node = QueueFront(&q);QueuePop(&q);if (node == NULL){break;}QueuePush(&q, node->left);QueuePush(&q, node->right);}while (!QueueEmpty(&q)){BTNode* node = QueueFront(&q);QueuePop(&q);if (node){QueueDestroy(&q);return 0;}}QueueDestroy(&q);return 1;
}

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

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

相关文章

从零开始利用MATLAB进行FPGA设计(六)用ADC采集信号教程1

黑金的教程做的实在太拉闸了,于是自己摸索信号采集模块的使用方法。 ADC模块:AN9238 FPGA开发板:AX7020;Xilinx 公司的 Zynq7000 系列的芯片XC7Z020-2CLG400I,400引脚 FBGA 封装。 往期回顾: 从零开始利…

测试驱动编程(4)模拟消除依赖

文章目录 测试驱动编程(4)模拟消除依赖模拟框架Mockito什么要模拟名词解释Mockito常用注解Mockito常用静态方法Mockito测试流程三部曲基础用法可变返回结果验证verfily对象监视spy 示例实战升级版井字游戏需求一需求二需求三 总结 测试驱动编程(4)模拟消除依赖 模拟框架Mockit…

YOLOv8架构详解

📌YOLOv8架构详解 YOLOv8 架构图YOLOv8 Backbone部分YOLOv8 Head部分Neck和Head结构 在视觉深度学习中,通常将模型分为 2~3 个组成部分:backbone、neck(可选) 和 head。 Backbone(主干网络)负责…

NTLite深度Windows系统镜像文件修改定制

计算机爱好者和技术宅的圈子里,NTLite是一个广受欢迎的名字,一款强大的Windows系统定制工具,允许用户对Windows安装镜像进行深度修改,从而打造出一个更加个性化、高效且精简的操作系统。无论是为了优化系统性能、移除不必要的组件,还是集成最新的更新和驱动,NTLite都能成…

java后端框架-MyBatis

一、概述 1、起源 MyBatis本是Apache下的开源项目,名为iBatis,2010年转投谷歌,从iBatis3.x开始更名为MyBatis 2、优点 (1)优秀的数据持久层框架(对jdbc做了轻量级封装) 3、特点 (1)对jdbc中接口进行封装的同时还提供了一些自己的类实现…

samba_ubuntu_share_vmbox_vmware

_____ Ubuntu 利用 samba 与 win 直接共享文件夹 _____ samba Samba - 维基百科,自由的百科全书 (wikipedia.org) 用于 win 和 unix 直接访问资源 samba 为选定的 unix 目录建立网络共享, 使得 win 用户可以像访问普通 win 下的文件夹那样来通过网络来…

npm : 无法加载文件 D:\Program Files\nodejs\npm.ps1,因为在此系统上禁止运行脚本

安装npm时出现如下提示: 出现这个错误信息,是系统禁止执行PowerShell的脚本。 出现的原因是,系统默认的执行策略是Restricted(默认设置),限制执行,所以会出现如上提示。 解决方法:…

Linux服务器配置ssh证书登录

1、ssh证书登录介绍 Linux服务器ssh登录有密码登录和证书登录两种。如果使用密码登录,容易遭受密码泄露或者暴力破解,我们可以使用ssh证书登录并禁止使用密码登录,ssh证书登录通过公钥和私钥来完成整个连接过程,公钥保存在服务器…

高维数组到向量的转换:两种方法的深度解析

新书上架~👇全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我👆,收藏下次不迷路┗|`O′|┛ 嗷~~ 目录 一、引言:高维数组的挑战与需求 二、方法一:使用NumPy库进行展平 示…

如何将md文件精确的转换成docx文件

如何将md文件转换成docx? 文章目录 如何将md文件转换成docx?一、如何将MD文件比较完美的转换成word呢?二、方法3 步骤1、下载一个可用的MarkDown编辑器2、下载Pandoc安装 三、来进行转化了 一、如何将MD文件比较完美的转换成word呢&#xff1…

从零开始学Vue3--根据目录结构自动生成路由

我们在测试或者小项目中经常遇到一个问题,就是加一个页面,就要在router.js中加一个路由,相当的麻烦,有没有办法可以根据目录结构自动生成路由呢? 想要自动生成路由,最重要的是能够获取指定目录下vue的路径…

开源代码分享(31)-计及电转气协同的含碳捕集与垃圾焚烧虚拟电厂优化调度

参考文献: [1]孙惠娟,刘昀,彭春华,等.计及电转气协同的含碳捕集与垃圾焚烧虚拟电厂优化调度[J].电网技术,2021,45(09):3534-3545.DOI:10.13335/j.1000-3673.pst.2020.1720. 1.摘要 为了促进多能源互补及能源低碳化,提出了计及电转气协同的含碳捕集与垃…

canfd与can2.0关系

canfd是can2.0的升级版, 支持canfd的设备就支持can2.0,但can2.0的设备不支持canfd 参考 是选CAN接口卡还是CANFD接口卡_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1Hh411K7Zn/?spm_id_from333.999.0.0 哪些STM32有CANFD外设 STM32G0, STM…

使用OrangePi KunPeng Pro部署AI模型

目录 一、OrangePi Kunpeng Pro简介二、环境搭建三、模型运行环境搭建(1)下载Ollama用于启动并运行大型语言模型(2)配置ollama系统服务(3)启动ollama服务(4)启动ollama(5)查看ollama运行状态四、模型部署(1)部署1.8b的qwen(2)部署2b的gemma(3)部署3.8的phi3(4)部署4b的qwen(5)部…

工作中有哪些超级好用的C/C++程序库?

视频和讲义发布在这里: B站链接

Android Ktor 网络请求框架

Ktor 是一个由 JetBrains 开发的用于 Kotlin 编程语言的应用框架,旨在创建高性能的异步服务器和客户端应用程序。由于完全基于 Kotlin 语言,Ktor 能够让开发者编写出简洁、可读性强且功能强大的代码,特别适合那些已经熟悉 Kotlin 的开发人员。…

调试记录-U盘枚举失败之LPM影响

现象 板子接部分U盘出现枚举失败,看log像是硬件信号问题,如: [ 29.186464] usb usb3-port1: Cannot enable. Maybe the USB cable is bad? [ 30.079624] usb usb3-port1: Cannot enable. Maybe the USB cable is bad? [ 30.080200]…

【高校科研前沿】南科大姜丽光课题组在地球物理学领域TOP期刊Geophys. Res. Lett.发表极端气候频发下水库蓄水状态的相关研究成果

文章简介 论文名称:Reservoir Filling Up Problems in a Changing Climate:Insights From CryoSat‐2 Altimetry 第一作者及单位:汪志伟(硕士研究生 南方科技大学环境学院) 通讯作者及单位:姜丽光(助理教…

JAVA:多线程常见的面试题和答案

请关注微信公众号:拾荒的小海螺 博客地址:http://lsk-ww.cn/ 1、并发编程三要素? 原 子 性 原子性指的是一个或者多个操作,要么全部执行并且在执行的过程中不被其他操作打断,要么就全部都不执行。可 见 性 可见性指多…

电脑录屏怎么录?7个电脑录屏软件免费版强势来袭,赶快收藏!

电脑录屏怎么录?相信很多小伙伴们都不知道怎么在Windows电脑上录屏吧?在当今社会,随着互联网的快速发展,越来越多的小伙伴们开始通过制作视频内容来分享知识、展示技能或者记录生活。电脑录屏成为了一种简单高效的方式&#xff0c…