【数据结构】非线性表----二叉树详解

news2024/9/24 3:24:41

二叉树与普通的树的本质上的区别实际上只有一个——子结点的数量

普通的树:任意数量的子结点
二叉树:只有两个子结点,也称为左孩子右孩子结点。

二叉树一共有五种形态:
1.空二叉树。
2.只有一个根结点。
3.根结点只有左子树。
4.根结点只有右子树。
5.根结点既有左子树又有右子树。

那么根据这五种形态就可以延申一个问题:如果有三个结点的二叉树,它能够有几种形态呢?
我们根据以上的五种形态,可以画出如下的形态
在这里插入图片描述

二叉树的特性:

满二叉树:除了第一层,其他层的结点都有两个子节点
在这里插入图片描述

完全二叉树:除了最后一层外,每层的节点数都达到最大,并且最后一层的节点都集中在左侧。(注:完全二叉树和满二叉树的关系相当于正方形和长方形的关系)

在这里插入图片描述

二叉搜索树:一种特定类型的二叉树,对于每个节点,左子树中的所有节点的值都小于该节点的值,右子树中的所有节点的值都大于该节点的值。

二叉树的公式

二叉树有几个重要的性质和公式,这是基于二叉树的特性的,有助于理解其结构和行为。以下是一些常见的二叉树相关公式和性质:

1. 节点数量与高度的关系

对于一棵具有 (h) 的高度的完全二叉树,节点的数量 (n) 的范围是:

  • 最小节点数:一棵只有根节点的二叉树,n=1
  • 最大节点数完全二叉树的节点数为 n=2^(h+1)−1。那么高度为 (h) 的二叉树的最大节点数为:
  • 在这里插入图片描述

2. 完全二叉树的叶子节点与高度

一棵完全二叉树的叶子节点 (L) 的数量与高度 (h) 的关系为:

在这里插入图片描述

这适用于从高度为0到高度为(h)的索引。

3. 内部节点数量

对于包含 (n) 个节点的二叉树,内部节点(即有至少一个子节点的节点)数量 (I) 和叶子节点数量 (L) 的关系为:

在这里插入图片描述

且总有:

在这里插入图片描述

这是因为内部结点仅仅比叶子节点少一个根节点

因此我们可以得出:

在这里插入图片描述

4. 节点深度与高度

对于每个节点,其深度(从根节点到该节点的边数) (d) 与树的高度 (h) 的关系是:

在这里插入图片描述

5. 完全二叉树的索引

完全二叉树中,如果父节点为 (i),则:

  • 左子节点为 (2i + 1)
  • 右子节点为 (2i + 2)
  • 反之,如果一个节点的索引为 j,其父节点的索引为***(j-1)/2***
  • 注意,本公式的条件是完全二叉树,但是实际上二叉树都符合这个公式

6.完全二叉树的度

  • 在这里插入图片描述

  • 度为0的永远比度为2 的多一个,即 N0=N2+1

  • 叶子节点的个数——即度为0的个数N0

7.完全二叉树的节点数量

在这里插入图片描述

8. 二叉搜索树的性质

在二叉搜索树中:

  • 所有左子树的节点值均小于节点值。
  • 所有右子树的节点值均大于节点值。

9. 平衡条件

对于一棵 AVL 树(自平衡的二叉搜索树),任何节点的两个子树的高度差至多为 1。这个性质确保了 AVL 树的高度始终保持在 (O(log n))。

二叉树的构建

我们知道,对于树来说,实际上它的子结点本身也是一棵树,那么我们通常就会使用递归的方法来构建树。

递归的介绍

递归,其中函数在其定义的过程中调用自身

递归通常由两个基本部分组成:

  1. 递归基(Base Case)

    • 这是停止递归调用的条件。当满足某个条件时,函数返回一个结果,而不再进行进一步的递归调用。
  2. 递归步骤(Recursive Step)

    • 这是函数如何将问题分解成更小的子问题的部分。函数调用自身来处理这些子问题,并通常会将结果合并以生成最终结果。
递归的优点

既然我们二叉树可以使用递归,那么递归都有哪些优点呢?

  1. 代码简洁性

    • 递归代码通常比迭代代码更简洁易读,能更直接地表达复杂问题的逻辑。
  2. 自然表达

    • 二叉树的构建和遍历自然适合递归方法,因为递归在某种角度来说与树的结构相辅相成。
递归的缺点

但是使用递归就会有一些缺点浮现。

  • 递归可能导致大量函数调用,从而增加系统资源的消耗(如栈空间)。
  • 如果没有适当地控制递归深度,可能会导致栈溢出(stack overflow)。

但总的来说,由于树的结构并不是特别复杂,并且往往调用的函数是其本身,其缺点也就微不足道了。

接下来我们就对二叉树的一系列操作进行解析。

二叉树定义
typedef int BTDataType;
typedef struct BinaryTreeNode
{
	BTDataType data;
	struct BinaryTreeNode* left;
	struct BinaryTreeNode* right;
}BTNode;

二叉树的创建和存储

二叉树可以使用数组和链表两种形式来进行存储,但是针对二叉树的特点,只有满二叉树或者完全二叉树适合使用数组存储。其他形式的树用数组存储反而会浪费空间,所以我们使用链表存储。

BTNode* BuyNode(BTDataType x)
{
	BTNode* node = (BTNode*)malloc(sizeof(BTNode));
	if (node == NULL)  
	{
		perror("malloc fail");
		return NULL;
	}
	node->data = x;
	node->left = NULL;
	node->right = NULL;

	return node;
}
计算树的深度

这里我们使用三位运算符可以更加直观和简短地计算树的深度

int TreeHeight1(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	int left = TreeHeight(root->left);//左子树的高度
	int right = TreeHeight(root->right);//右子树的高度
	return left > right ? left + 1 : right + 1;//当前树的高度=左右子树最大值+1
}

遍历

对于二叉树的遍历,这其中大有说法。
同时它也是递归思想的经典案例,我们进行仔细分析。

我们知道树的结构,从结点来看,二叉树可以看作父亲结点、左孩子结点、右孩子结点,我们需要获得孩子节点只需要直接指向它的左孩子结点或者右孩子结点即可;而从横面来看,二叉树是具有层数的,每一层的结点数都与2的倍数有关;

那么我们要遍历所有的结点的时候,我们就衍生出了三种不同的遍历方法,它们的不同是根据访问父结点的顺序来决定的:前序遍历(先序遍历)、中序遍历、后序遍历。举个例子,先序遍历就是最先访问父结点,再访问左孩子结点,最后访问右孩子结点。
鉴于递归的特点,当我们访问孩子结点的时候,又可以将其再作为一个父节点,从而再去访问它的孩子结点,一直递归下去,直到遍历完所有的结点。
接下来我们分别编写三种遍历的代码。

先序遍历:父节点->左孩子结点->右孩子结点

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

中序遍历:左孩子结点->父节点->右孩子结点

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

后序遍历:左孩子结点->右孩子结点->父节点

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

通过上述的代码我们其实可以发现,printf("%d ", root->data);这个语句实际上就是父节点的位置,而PostOrder(root->left); 和PostOrder(root->right); 就是左孩子结点和右孩子结点的分别位置。从书面上来看,使用递归的写法使得代码十分简洁易懂,并且具有很强的逻辑性帮助理解二叉树的本质。

你以为遍历就这么完了吗?其实并没有。我们来看一个例子。

在这里插入图片描述

现在请你根据这个二叉树写出它中序遍历之后的排列顺序。

你以为的:中序遍历就是按照左->父->右的顺序直接遍历就行了,

H->D->I->B->E->A->F->C->G

但是很遗憾,这样写是错的。因为空结点NULL也是结点,我们不能忽视它。

你再以为的:既然空结点也是结点,那只需要将E、F、G的左右孩子结点(即空结点)也表示出来即可。

H->D->I->B->NULL->E->NULL->A->NULL->F->NULL->C->NULL->G->NULL

但是很遗憾,这样写也是错的。你忽视掉了H和I。

实际上的:

NULL->H->NULL->D->NULL->I->B->NULL->E->NULL->A->NULL->F->NULL->C->NULL->G->NULL

据此我们可以知道的是,遍历的时候我们不能忽视空结点。在实际解决的时候我们需要先访问所有的结点,如果访问完某个结点为空才可以跳过它去访问下一个结点,而不是忽略空结点。

除了以上三种遍历以外,还有一种遍历方法,这种方式是直接一层一层地进行遍历,也叫做层序遍历。

层序遍历:第一层->第二层->第三层->…->第n层

//层序遍历
//思路:首先建一个队列,根节点入队,然后出队,打印队首元素,左右子树入队,直到队列为空
void LevelOrder(BTNode* root)
{
	Queue q;//首先建一个队列
	QInit(&q);//初始化队列
	if (root == NULL)
	{
		return NULL;
	}
	if (root)
	{
		QPush(&q, root);//根节点入队
	}
	while (!QEmpty(&q))//队列不为空
	{
		BTNode* front = QFront(&q);//队首元素
		printf("%d ", front->data);//打印队首元素
		if (front->left) //左子树入队
		{
			QPush(&q, front->left);
		}
		if (front->right) //右子树入队
		{
			QPush(&q, front->right);
		}
	}
	QDestroy(&q);//销毁队列
}
销毁二叉树
//销毁二叉树
void DestroyTree(BTNode* root)
{
	if (root == NULL)
		return;
	DestroyTree(root->left);
	DestroyTree(root->right);
	free(root);
}

以上仅仅是对二叉树典型概念和用法的大致讲解,实际上二叉树涉及到的内容还有很多,例如线索二叉树的构建、树与二叉树之间的转换等等,而这些概念将会在后续的补充中分开进行讲解。

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

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

相关文章

【OpenCV C++20 学习笔记】图像缩放-高斯金字塔

图像缩放-高斯金字塔 原理高斯金字塔 代码实现放大缩小形成金字塔 原理 在图像处理中,经常需要将图像转化成不同的尺寸,即放大或缩小。 除了直接用resize()函数重新设置图片尺寸,另一种常用的方法就是“图像金字塔”。 图像金字塔是从底层的…

vector的底层原理剖析及其实现

vector 一、定义二、常用接口及模拟实现三、vector迭代器失效问题四、使用memcpy拷贝会出现的问题五、二维数组vector<vector< T >> vv 一、定义 vector 是 C 标准模板库&#xff08;Standard Template Library, STL&#xff09;中的一个非常有用的容器。它是一个…

23款奔驰GLS450加装原厂电吸门配置,提升车辆舒适性和便利性

今天是一台22款奔驰GLS450&#xff0c;车主是佛山的 以前被不良商家坑了 装了副厂的电吸门 刚开始就很正常 用了半年之后 就开始开不了门&#xff0c;被锁在里面&#xff0c;刚开始车主以为是零件坏了 后来越来越频繁&#xff0c;本来是为了家里老人小孩关门方便而升级的&#…

J031_使用TCP协议支持与多个客户端同时通信

一、需求文档 使用TCP协议支持与多个客户端同时通信。 1.1 Client package com.itheima.tcp2;import java.io.DataOutputStream; import java.io.OutputStream; import java.net.Socket; import java.util.Scanner;public class Client {public static void main(String[] a…

软件设计之Java入门视频(22)

软件设计之Java入门视频(22) 视频教程来自B站尚硅谷&#xff1a; 尚硅谷Java入门视频教程&#xff0c;宋红康java基础视频 相关文件资料&#xff08;百度网盘&#xff09; 提取密码&#xff1a;8op3 idea 下载可以关注 软件管家 公众号 学习内容&#xff1a; 该视频共分为1-7…

Flask 介绍

Flask 介绍 为什么要学 Flask框架对比设计哲学功能特点适用场景学习曲线总结 Flask 的特点Flask 常用扩展包Flask 的基本组件Flask 的应用场景官方文档官方文档链接文档内容概述学习建议 Flask 是一个使用 Python 编写的轻量级 Web 应用框架。它旨在让 Web 开发变得快速、简单且…

ACl访问控制实验

要求&#xff1a;PC1可以telnet登录r1&#xff0c;不能ping通r1&#xff0c;pc1可以ping通r2&#xff0c;但不能telnet登录r2&#xff0c;pc2的所有限制与pc1相反 实验思路&#xff1a;因为华为的ensp默认允许所有&#xff0c;所以只写拒绝规则就行 rule 5 deny icmp source 19…

只需0.5秒 Stability AI新模型超快生成3D图像

生成式人工智能&#xff08;AI&#xff09;明星初创公司Stability AI 8月发布最新突破性3D模型Stable Fast 3D&#xff0c;将单张图片生成3D图像的速度大幅提升。Stability AI今年3月发布的3D模型SV3D需要多达10分钟生成3D资产&#xff0c;基于TripoSR的新模型Stable Fast 3D完…

【面试官:我看你SQL语句掌握的怎么样?面试SQL语句专题3】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

【教程】Python语言的地球科学常见数据—— IMS积雪覆盖数据的处理

将ASCII数据转化为netCDF数据、分析新疆北疆、青藏高原和东北地区气候态积雪分布、分析新疆北疆、青藏高原和东北地区积雪面积变化规律。 美国国家冰雪中心&#xff08;NSIDC&#xff09;从 1997 年 2 月至今的北半球雪盖和海冰的地图。这些数据以 ASCII 文本和 GeoTIFF 格式提…

AIWEB1综合靶场通关教程,从外网打到内网【附靶场环境】

前言 靶场获取后台回【aiweb1】 下载之后设置为nat模式 启动即可&#xff0c;不需要登录 靶机复现 主机发现 访问即可 信息收集robots.txt文件 访问尝试&#xff0c;原来是什么也没有的&#xff0c;404 我们去访问这个上级目录&#xff0c;发现有一个id 注入测试 语法错误&am…

基于Protobuf的RPC

先上UserServer提供服务的函数要求proto文件内容&#xff1a; syntax"proto3"; package fixbug; option cc_generic_servicestrue; message LoginRequest {bytes name1;bytes pwd2; } message LoginResponse {ResultCode result1;bool sucess2; } #调用远程服务的入…

JAVA游戏源码:跑酷|大学生练手项目

学习java朋友们&#xff0c;福利来了&#xff0c;今天小编给大家带来了一款跑酷源码。注意&#xff1a;此源码仅供学习使用!! 源码搭建和讲解 启动main入口&#xff1a; //************************************************************************ // ************完整源码…

AcWing食物链

Q1&#xff1a;怎么判断X和Y是不是同类? A:判断这俩是不是在一个集合中,如果在同一个集合中&#xff0c;那么判断X到祖先节点的距离D[X]和D[Y]到祖先节点的距离是否有D[X]%3D[Y]%3,也就是3同余 若果是&#xff0c;那么是同类。如果X和Y不在一个集合里面&#xff0c;那么把X和Y…

护网总结汇报PPT一键生成,还要啥售前工程师

【文末送&#xff1a;技战法】 干技术的&#xff0c;特别是干安服的&#xff0c;你让我日个站觉得没问题&#xff0c;你让我写个文档我挠挠头&#xff0c;抓抓背也能凑一篇&#xff0c;但是你要让我写个ppt&#xff0c;那我觉得你在为难我。 报告我都写好了&#xff0c;为啥还…

Eclipse Debug 配置

创建和使用 Debug 配置 Eclipse Debug 配置类似于运行配置但它是用于在调试模式下开启应用。 打开 Debug 配置对话框步骤为&#xff1a;Run > Debug Configurations 。 从左侧列表中选择 "Java Application" 选项来选择要调试的 Java 代码。 对话框中的描述信息…

24年电赛——自动行驶小车(H题)MSPM0G3507-编码电机驱动与通用PID

一、编码电机驱动 编码电机的详情可以查看此篇文章&#xff1a; stm32平衡小车--&#xff08;1&#xff09;JGB-520减速电机tb6612&#xff08;附测试代码&#xff09;_jgb520-CSDN博客 简单来说&#xff0c;编码电机的驱动主要是给一个 PWM 和一个正负级就能驱动。PWM 的大小…

AI PC处理器架-低功耗、NPU算力、大模型

AI PC处理器架构变化&#xff1a;ARM低功耗、引入NPU算力、大模型落地端侧 ARM架构以简洁的指令集设计&#xff0c;快速执行每条命令&#xff0c;实现低功耗下的高效性能。其核心理念是节能和效率&#xff0c;为电池驱动设备提供了理想选择。相较之下&#xff0c;x86架构虽指令…

Linux--shell脚本语言—/—<1>

一、shell简介 Shell是一种程序设计语言。作为命令语言&#xff0c;它交互式解释和执行用户输入的命令或者自动地解释和执行预先设定好的一连串的命令&#xff1b;作为程序设计语言&#xff0c;它定义了各种变量和参数&#xff0c;并提供了许多在高级语言中才具有的控制结构&am…

【practise】大数相加、大数相乘

通常&#xff0c;我们的int、long long类型都有最大的数字上限&#xff0c;也就是说再大了会有溢出问题&#xff0c;那么很大的数字是怎么进行运算的呢&#xff1f; 其中一种方法是把很大的数字转变成字符串存放到string中&#xff0c;然后用代码对字符串进行处理&#xff0c;…