二叉树【数据结构】

目录

  • 二叉树
    • 1. 二叉树定义
      • 二叉树的存储定义
    • 2. 遍历二叉树
      • (1) 前序遍历
      • (2) 中序遍历
      • (3) 后序遍历
      • (4) 层序遍历
    • 3. 二叉树的相关操作
      • (1) 二叉树的初始化
      • (2) 二叉树的结点的手动创建
      • (3) 二叉树结点的个数
      • (4) 二叉树叶子结点的个数
      • (5) 二叉树的高度
      • (6) 第k层结点个数
      • (7) 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
      • (8) 二叉树查找值为x的结点
      • (9) 判断是否为完全二叉树
      • (10) 二叉树的销毁

前言:
之前讲到过:
数据结构:是存在一种或多种特定关系的数据元素的集合。其中,一种或多种特定关系,会分为:逻辑结构和物理结构(也叫存储结构)。

  • 逻辑结构:数据对象中数据元素之间的相互关系。其中逻辑结构又分为多种
    • 集合结构:集合结构中的元素属于同集合,没有其他关系
    • 线性结构:数据元素之间是一对一的关系
    • 树形结构:数据元素之间是一对多的层次关系
    • 图形结构:数据元素之间是多对多的的关系

物理(存储)结构:

  • 线性(顺序)存储
  • 链式存储

首先简单滴介绍一下树

树:是一种对多的层次关系
树是一种非线性的数据结构,由节点(或称为顶点)和边组成。它可以表示为一个层次结构,其中每个节点都可以有零个或多个子节点。树的一个节点称为其父节点的子节点,而父节点则称为其子节点的父节点。树的顶部节点称为根节点,没有父节点的节点称为叶节点。树可以用于表示层次关系,如文件系统的目录结构或组织结构图。
在这里插入图片描述
关于结点的分类:

  • 结点的度:结点拥有的子树
  • 叶子结点:度为0的结点
  • 分支结点:度不为0的结点
  • 树的度:分支结点度的最大值

二叉树

1. 二叉树定义

二叉树定义:由一个根结点和两棵互不相交、分别称为根结点的左右子树的二叉树组成
在这里插入图片描述
二叉树的特点:

  • 每个结点最多有两棵子树,所以二叉树中不存在度大于2的结点
  • 左右子树是有顺序的不能颠倒

二叉树的基本形态:

  • 空二叉树
  • 只有一个根结点
  • 根节点只有左子树
  • 根结点只有左子树
  • 根结点既有左子树又有右子树

在这里插入图片描述
满二叉树: 在一棵二叉树中,所有分支结点都存在左子树和右子树,并且所有叶子都在同一层上
满二叉树的特点:

  • 叶子结点只能出现在最下一层
  • 分叶子结点的度一定是2
  • 在相同深度的二叉树中,满二叉树的结点个数最多,叶子树最多

在这里插入图片描述

完全二叉树:
除了最后一层外,也就是前n-1层的结点都必须是满的,最后一层的结点从左到右连续存在,不能有间隔
完全二叉树的特点:

  1. 叶子只能出现在最后两层
  2. 最后一层从左到有是连续的

二叉树的存储定义

二叉树的顺序存储
二叉树的链式存储

这里我们采用链式存储方式

存储结构:

代码展示

typedef int BTDataType;typedef struct BinaryTreeNode 
{BTDataType val;	//二叉树的值struct BinaryTreeNode* left;	//指向左子树struct BinaryTreeNode* right;	//指向右子树
}BinaryTree;

2. 遍历二叉树

二叉树遍历:从根结点出发,按某种次序访问二叉树中的所有结点,使得每个结点被访问一次且仅被访问一次

因为我们习惯从左到右的习惯,二叉树的遍历方法分为 前序、中序、后序、层序遍历

在这里插入图片描述

这里的二叉树的存储结构我们使用链式存储

(1) 前序遍历

前序遍历(先根遍历): 若二叉树为空,返回空,否则访问根结点,然后 前序遍历左子树,再 前序遍历右子树。

在这里插入图片描述

代码展示

//前序遍历二叉树
void PreOrderTree(BinaryTree* root) 
{//如果遇到空打印N 并返回函数调用的地方if (root == NULL){printf("N ");return;}printf("%d ",root->val);	//打印结点的值PreOrderTree(root->left);	//先序遍历左子树PreOrderTree(root->right);	//先序遍历右子树
}

如图:
在这里插入图片描述

(2) 中序遍历

中序遍历(中根遍历): 若二叉树为空,返回空,否则 中序遍历左子树,再访问根结点,中序遍历右子树。
在这里插入图片描述
代码展示

//中序遍历二叉树
void InOrderTree(BinaryTree* root)
{//如果遇到空打印N 并返回函数调用的地方if (root == NULL){printf("N ");return;}InOrderTree(root->left);	//中序遍历左子树printf("%d ", root->val);InOrderTree(root->right);	//中序遍历右子树
}

(3) 后序遍历

后序遍历(后根遍历): 若二叉树为空,返回空,否则 后序遍历左子树 , 后序遍历右子树 , 再访问根结点。

在这里插入图片描述
“#” 表示空
在这里插入图片描述

(4) 层序遍历

层序遍历:这里我们需要借助队列来实现,原理:上一层出来会依次带入下一层进入(队列:先进先出)
在这里插入图片描述

思路:

  1. 根结点不为空就进队(注意是结点进队,不是结点的值进队)
  2. 队头结点进行出队,同时下一层进队(即根结点的左右子树进队)
  3. 当队列为空时,层序遍历完成

在这里插入图片描述

代码展示

//二叉树的层序遍历
void BinaryTreeLevelOrder(BinaryTree* root) 
{Queue pq;	//定义队列变量//初始化对队列InitQueue(&pq);//当root不为空时,就进队if (root != NULL){//结点入队QueuePush(&pq,root);}//队列不为空就继续进行层序遍历//队列为空时,层序遍历完成int levelsize = 1;while ( !QueueEmpty(&pq) ){//一层一层出while (levelsize--){//队头出队,队尾进队//先获取队头结点BinaryTree* headnode = QueueFront(&pq);//上一层出队QueuePop(&pq);//打印队头结点的值printf("%d- ", headnode->val);//下一层进队,即队头结点的左右子树结点入队//前提时,左右子树结点不能为空if (headnode->left){QueuePush(&pq, headnode->left);}if (headnode->right){QueuePush(&pq, headnode->right);}}printf("\n");levelsize = QueueSize(&pq);}//销毁队列QueueDestroy(&pq);}

3. 二叉树的相关操作

(1) 二叉树的初始化

这里使用动态函数来开辟二叉树的结点,给结点赋值同时进行把该节点的左右子树暂时置NULL,最后通过返回该结点的地址(避免使用二级指针了)

代码展示

//二叉树的初始化
BinaryTree* InitTreeNode(int x) 
{//动态开辟BinaryTree* node = (BinaryTree*)malloc(sizeof(BinaryTree));assert(node); //断言避免指针为空node->val = x;node->left = NULL;node->right = NULL;return node;	//通过返回函数来进行创建结点
}

在这里插入图片描述

(2) 二叉树的结点的手动创建

有时候方便调试,需要手动创建二叉树
同创建完成后通过返回根节点的地址

在这里插入图片描述

代码展示

//二叉树的手动构建
BinaryTree* CreateTree() 
{BinaryTree* node1 = InitTreeNode(1);BinaryTree* node2 = InitTreeNode(2);BinaryTree* node3 = InitTreeNode(3);BinaryTree* node4 = InitTreeNode(4);BinaryTree* node5 = InitTreeNode(5);BinaryTree* node6 = InitTreeNode(6);node1->left = node2;node1->right = node4;node2->left = node3;node4->left = node5;node4->right = node6;return node1;	//返回根结点}

(3) 二叉树结点的个数

即计算有多少结点
先从根结点开始,若根节点为空,就返回0,如果不为空,递归左子树,然后递归右子树。(当然这里递归顺序没有要求,也可以先进行递归右子树然后再递归左子树)

使用分治思想:
树的结点个数 = 左子树结点个数 + 右子树结点个数 + 1 ;

在这里插入图片描述

代码展示

//计算二叉树结点的个数
int BinaryTreeSize(BinaryTree* root) 
{//根结点开始//访问左子树,访问右子树if (root == NULL)return 0;return BinaryTreeSize(root->left)+BinaryTreeSize(root->right) + 1;
}

优化一下:

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

(4) 二叉树叶子结点的个数

叶子结点:结点度为0的结点
使用递归,返回条件为

  1. 如果根结点为空返回 0
  2. 如果根结点不为空 且 左右子树都为空 说明是叶子返回1
  3. root 不是空,也不是叶子,分治法,叶子数 = 左子树的叶子+右子树的叶子

代码展示

//计算叶子结点的个数
int BinaryTreeLeafSzie(BinaryTree* root) 
{	//分治法// 叶子结点树 = 左子树叶子 + 右子树叶子//结点为空返回0if (root == NULL)return 0;//root不为空,左右子树为空是叶子,返回1if (root->left == NULL && root->right == NULL)return 1;//root不为空且左右子树都不为空,继续递归左右子树return BinaryTreeLeafSzie(root->left) + BinaryTreeLeafSzie(root->right);
}

(5) 二叉树的高度

分治法
树高 = 左子树与右子树中较高的树 +1
递归条件

  1. root 为空 返回 0
  2. root 不为空 计算左右子树中较高的,返回较高的+1

代码展示

//计算二叉树的高度
int BinaryTreeHeight(BinaryTree* root) 
{//树高 = 左子树与右子树中较高的树 +1if (root == NULL)return 0;//记录左子树高int leftHeight = BinaryTreeHeight(root->left);//记录右子树高int rightHeight = BinaryTreeHeight(root->right);//返回较高树同时+1return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
}

方法二:

//计算树高方法二
int BinaryTreeHeight(BinaryTree* root)
{//树高 = 左子树与右子树中较高的树 +1if (root == NULL)return 0;return fmax(BinaryTreeHeight(root->left),BinaryTreeHeight(root->right))+1;
}

如图:
在这里插入图片描述
在这里插入图片描述

(6) 第k层结点个数

计算第k 层的结点个数,这里我们可以转化为去求 左子树的 k - 1 层和右子树的 k - 1 层 的结点的个数

递归条件:

  1. root 为空,返回0
  2. root不为空且 k == 1,返回1
  3. root不为空且 k > 1, 返回左子树的 k - 1 层 + 右子树的 k - 1 层

在这里插入图片描述
代码展示

//二叉树第k层结点的个数
int BinaryTreeLevelSizeK(BinaryTree* root, int k) 
{assert(k >= 1); //层数最小为1//第k层结点个数 = 左子树k-1层 + 右子树k-1层 的结点个数if (root == NULL)return 0;//结点不为空,k == 1,返回1if (k == 1)return 1;//k>1,继续递归左右子树的 k-1 一层return BinaryTreeLevelSizeK(root->left,k-1) + BinaryTreeLevelSizeK(root->right,k - 1);
}

(7) 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树

当元素的值是 ‘#’ 时,表示为空,数组下标加一;不是‘#’时,动态分配内存空间并给结点赋值。然后递归左右子树,最后返回根结点

代码展示

// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
BinaryTree* BinaryTreeCreate(BTDataType* arr, int* pi) 
{//# 表示为空if (arr[*(pi)] == '#'){(*pi)++;return NULL;}//不是#,就动态分配内存空间BinaryTree* root = (BinaryTree*)malloc(sizeof(BinaryTree));if (root == NULL){perror("malloc fail");exit(-1);}//结点赋值root->val = arr[(*pi)++];//递归左右子树root->left = BinaryTreeCreate(arr,pi);root->right = BinaryTreeCreate(arr,pi);//返回根结点return root;
}

如图:
在这里插入图片描述

(8) 二叉树查找值为x的结点

首先当根结点为空直接返回空,当root不为空且结点值等于x的时候直接返回结点,当root不为空且结点值不等于x的时候,递归左右子树(通过记录左右子树的结点来避免重复去找,还需注意左右结点为空的情况)

代码展示

//二叉树查找值为x的结点
BinaryTree* BinaryTreeFind(BinaryTree* root, BTDataType x)
{//结点为空直接返回空if (root == NULL)return NULL;//root不为空,结点值 = x ,就返回结点if (root->val == x) {return root;}//root不为空,结点值 != x ,递归左右子树//但是递归左右子树时,注意子树为空的情况//记录结点,避免重复去找BinaryTree* left = BinaryTreeFind(root->left, x);if (left)return left;BinaryTree* right = BinaryTreeFind(root->right, x);if (right)return right;//左右结点都为空,返回空return NULL;
}

在这里插入图片描述

(9) 判断是否为完全二叉树

除了最后一层外,也就是前n-1层的结点都必须是满的,最后一层的结点从左到右连续存在,不能有间隔

完全二叉树
在这里插入图片描述

不是完全二叉树 ,情况一:
在这里插入图片描述
情况二 :

在这里插入图片描述

判断是否为完全二叉树,我们需要进行借助层序遍历,层序遍历时当遇到结点为空时,跳出循环。然后在借助一次层序遍历,如果遇到了不为空的结点,即不是完全二叉树。否则是完全二叉树。

代码展示

//判断是否为完全二叉树
bool BinaryTreeComplete(BinaryTree* root)
{//借助层序遍历//遇到空时,再层序遍历后面都为空,就是完全二叉树//否则不是Queue pq;QueueInit(&pq);//结点不为空就进队列if (root){QueuePush(&pq, root);}while (!QueueEmpty(&pq)){//先取队头BinaryTree* headnode = QueueFront(&pq);//上一层出队QueuePop(&pq);//只要遇到空就停止层序遍历if (headnode == NULL){break;	//跳出循环}//下一层进队QueuePush(&pq,headnode->left);QueuePush(&pq,headnode->right);}//再一次层序遍历,只要遇到结点不为空就不是完全二叉树,否则是完全二叉树while (!QueueEmpty(&pq)) {//先取队头BinaryTree* headnode = QueueFront(&pq);//上一层出队QueuePop(&pq);if (headnode)	//结点不为空,此树不是完全二叉树return false;	}return true;
}

上述中使用了两次层序遍历

下方的另种方法只使用了一次层序遍历的方法,通过记录值进行确定,当遇到空的时候,记录值为true,当在层序遍历中,发现记录值为true 且 结点不为空,说明不是完全二叉树

//判断是否为完全二叉树 法二
bool BinaryTreeComplete(BinaryTree* root) 
{//使用一次层序遍历,遍历时,对空进行记录Queue pq;QueueInit(&pq);//如果根结点不为空,结点入队if (root){//注意进队的是结点,不是结点的值QueuePush(&pq, root);}//使用bool类型进行记录bool hasEmpty = false;while (!QueueEmpty(&pq)){//获取队头元素QDataType headnode = QueueFront(&pq);//出队QueuePop(&pq);if (headnode == NULL) {//当队头元素为空时,记录值为truehasEmpty = true;}else{//当队头结点不为空时,记录值为true,说明前面已经遇到空且后面的结点有不为空的(即不是完全二叉树)if (hasEmpty == true){//不是完全二叉树return false;}//记录值为false时,没有遇到空//继续层序遍历//前一层出队,后一层入队QueuePush(&pq,headnode->left);QueuePush(&pq,headnode->right);}}return true;
}

(10) 二叉树的销毁

把二叉树的所有不为空结点进行 一 一 释放

代码展示

//二叉树的销毁
void BinaryTreeDestroy(BinaryTree* root) 
{//需要把二叉树的所有不为空结点进行一一释放//root 为空 直接返回if (root == NULL)return;//root不为空,递归左右子树BinaryTreeDestroy(root->left);BinaryTreeDestroy(root->right);free(root);
}

在这里插入图片描述

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

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

相关文章

科技巨头三巨头:百度、阿里巴巴、腾讯的崛起与角力

导言 百度、阿里巴巴、腾讯被誉为中国科技领域的“三巨头”,在搜索、电商、社交等多个领域取得了巨大成功。本文将深入探讨这三家公司的崛起原因、核心业务、战略布局以及彼此之间的竞争和合作关系。 1. 百度的搜索帝国 搜索引擎: 百度以其搜索…

Java 序列化机制详解

Java 序列化机制是一种将对象转换为字节流的过程,以便在网络上传输或保存到文件中,并能在需要时将字节流还原为对象。这一机制通过实现 java.io.Serializable 接口来实现,同时涉及到一些关键概念和注意事项。 Serializable 接口 Serializabl…

开发知识点-09Rust

Rust Rust 语言通常用于编写系统级软件、网络服务器和高性能应用程序,它具有以下特点:1. 高性能和内存安全:Rust 在保证高性能的同时,利用其所有权模型和借用检查器等特性确保内存安全,避免了 C/C 等语言的内存错误和崩…

瑞友天翼应用虚拟化系统 多处SQL 注入漏洞复现(可RCE)

0x01 产品简介 瑞友天翼应用虚拟化系统是西安瑞友信息技术资讯有限公司研发的具有自主知识产权,基于服务器计算架构的应用虚拟化平台。它将用户各种应用软件集中部署在瑞友天翼服务器(群)上,客户端通过WEB即可快速安全的访问经服务器上授权的应用软件,实现集中应用、远程接…

Pytorch nn.Linear()的基本用法与原理详解及全连接层简介

主要引用参考: https://blog.csdn.net/zhaohongfei_358/article/details/122797190 https://blog.csdn.net/weixin_43135178/article/details/118735850 nn.Linear的基本定义 nn.Linear定义一个神经网络的线性层,方法签名如下: torch.nn.Li…

生日蜡烛C语言

分析&#xff1a;假设这个人只能活到100岁&#xff0c;如果不这样规定的话&#xff0c;那么这个人就可以假设活到老236岁&#xff0c;直接一次吹236个蜡烛&#xff0c;我们就枚举出所以情况&#xff0c;从一岁开始。 #include <stdio.h> int f(int a,int b){//计算从a到…

视频素材网站全新上线,海量高清视频等你来探索~

亲爱的视频制作爱好者们&#xff0c;好消息来啦&#xff01;我们的视频素材网站全新上线啦&#xff01;这次我们为大家带来了海量的高清视频素材&#xff0c;无论是风景、城市、人物、动物还是各种特效、背景等&#xff0c;应有尽有&#xff0c;满足您在视频制作过程中的各种需…

【神器】wakatime代码时间追踪工具

文章目录 wakatime简介支持的IDE安装步骤API文档插件费用写在最后 wakatime简介 wakatime就是一个IDE插件&#xff0c;一个代码时间追踪工具。可自动获取码编码时长和度量指标&#xff0c;以产生很多的coding图形报表。这些指标图形可以为开发者统计coding信息&#xff0c;比如…

【MySQL】:复合查询

复合查询 一.多表查询二.自连接三.子查询1.单行子查询2.多行子查询3.多列子查询4.在from语句里使用子查询5.合并查询 准备三张表 emp表 dept表 salgrade表 一.多表查询 实际开发中往往数据来自不同的表&#xff0c;所以需要多表查询。我们用一个简单的公司管理系统&#xff0c…

HPM6750系列--第十一篇 Uart讲解(轮询模式)

一、目的 在介绍完GPIO的相关内容下一个必须介绍的就是uart了&#xff0c;因为串口一个主要用途就是用于调试信息打印。 HPM6750在uart的配置上也是相当炸裂&#xff0c;有17个串口&#xff1b;结合HPM6750的高主频高内存&#xff0c;完全可以作为一个串口服务器。 ​​​​​​…

智能优化算法应用:基于天牛须算法3D无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于天牛须算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于天牛须算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.天牛须算法4.实验参数设定5.算法结果6.参考文…

MLOps在极狐GitLab 的现状和前瞻

什么是 MLOps 首先我们可以这么定义机器学习&#xff08;Machine Learning&#xff09;&#xff1a;通过一组工具和算法&#xff0c;从给定数据集中提取信息以进行具有一定程度不确定性的预测&#xff0c;借助于这些预测增强用户体验或推动内部决策。 同一般的软件研发流程比…

【lesson17】MySQL表的基本操作--表去重、聚合函数和group by

文章目录 MySQL表的基本操作介绍插入结果查询&#xff08;表去重&#xff09;建表插入数据操作 聚合函数建表插入数据操作 group by&#xff08;分组&#xff09;建表插入数据操作 MySQL表的基本操作介绍 CRUD : Create(创建), Retrieve(读取)&#xff0c;Update(更新)&#x…

【TB作品】STM32 PWM之实现呼吸灯,STM32F103RCT6,晨启

文章目录 完整工程参考资料实验过程 实验任务&#xff1a; 1&#xff1a;实现PWM呼吸灯&#xff0c;定时器产生PWM&#xff0c;控制实验板上的LED灯亮灭&#xff1b; 2&#xff1a;通过任意两个按键切换PWM呼吸灯输出到两个不同的LED灯&#xff0c;实现亮灭效果&#xff1b; 3&…

Axure的案例演示

增删改查&#xff1a; 在中继器里面展示照片

创建型模式之抽象工厂模式

一、概述 1、抽象工厂模式&#xff1a;提供一个创建一系列相关或相互依赖对象的接口&#xff0c;而无需指定它们具体的类。 2、抽象工厂模式&#xff1a;一个工厂可以生产一系列产品&#xff08;一族产品&#xff09;&#xff0c;极大减少了工厂类的数量 3、抽象工厂模式&am…

众和策略:加强经济监测预测预警 加大宏观调控力度

12月17日至18日&#xff0c;全国展开和革新作业会议在京举行&#xff0c;整理总结2023年展开革新作业&#xff0c;组织布置2024年展开革新关键使命。会议指出&#xff0c;中心经济作业会议对本年经济作业作了全面体系总结&#xff0c;侧重我国经济全体上升向好&#xff0c;全年…

选择合适教育管理软件:必须考虑的10个关键问题

随着教育行业的迅速数字化&#xff0c;学校要能够提供最新的管理和教育方法。大家逐渐意识到技术让运营变得更容易、更有效率。 不过首先我们需要找到一个能满足需求的应用程序。面对众多的选择&#xff0c;你该如何选择一个合适的平台呢&#xff1f;当然&#xff0c;没有人想…

MYSQL中使用IN,在xml文件中怎么写?

MYSQL&#xff1a; Spring中&#xff1a; mysql中IN后边的集合&#xff0c;在后端中使用集合代替&#xff0c;其他的没有什么注意的&#xff0c;还需要了解foreach 语法即可。

Spark编程实验一:Spark和Hadoop的安装使用

目录 一、目的与要求 二、实验内容 三、实验步骤 1、安装Hadoop和Spark 2、HDFS常用操作 3、Spark读取文件系统的数据 四、结果分析与实验体会 一、目的与要求 1、掌握在Linux虚拟机中安装Hadoop和Spark的方法&#xff1b; 2、熟悉HDFS的基本使用方法&#xff1b; 3、掌…