首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >【数据结构】非线性表----二叉树详解

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

作者头像
Skrrapper
发布于 2024-08-05 01:52:44
发布于 2024-08-05 01:52:44
12200
代码可运行
举报
文章被收录于专栏:技术分享技术分享
运行总次数:0
代码可运行

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

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

二叉树一共有五种形态: 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)。

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

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

二叉树定义
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
typedef int BTDataType;
typedef struct BinaryTreeNode
{
	BTDataType data;
	struct BinaryTreeNode* left;
	struct BinaryTreeNode* right;
}BTNode;
二叉树的创建和存储

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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;
}
计算树的深度

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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的倍数有关;

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

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//前序遍历
void PreOrder(BTNode* root)
{
	if (root == NULL)
	{
		return;
	}
	printf("%d ", root->data);
	PreOrder(root->left);
	PreOrder(root->right);
}

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//中序遍历
void MidOrder(BTNode* root)
{
	if (root == NULL) 
	{
		return;
	}
	MidOrder(root->left); 
	printf("%d ", root->data);
	MidOrder(root->right);  
}

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//后序遍历
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); 就是左孩子结点和右孩子结点的分别位置。从书面上来看,使用递归的写法使得代码十分简洁易懂,并且具有很强的逻辑性帮助理解二叉树的本质。

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

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

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
H->D->I->B->E->A->F->C->G

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

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
H->D->I->B->NULL->E->NULL->A->NULL->F->NULL->C->NULL->G->NULL

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

实际上的:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
NULL->H->NULL->D->NULL->I->B->NULL->E->NULL->A->NULL->F->NULL->C->NULL->G->NULL

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

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

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//层序遍历
//思路:首先建一个队列,根节点入队,然后出队,打印队首元素,左右子树入队,直到队列为空
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);//销毁队列
}
销毁二叉树
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//销毁二叉树
void DestroyTree(BTNode* root)
{
	if (root == NULL)
		return;
	DestroyTree(root->left);
	DestroyTree(root->right);
	free(root);
}

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

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-08-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
[C++][opengl]利用glut和gluax画矩形等
pattern是由1和0组成的长度为16的序列,从最低位开始看,如果为1,则直线上接下来应该画的factor个点将被画为实的;如果为0,则直线上接下来应该画的factor个点将被画为虚的。 以下是一些例子:
云未归来
2025/07/20
730
[C++][opengl]利用glut和gluax画矩形等
6.5编程实例-立方体透视投影
GLint winWidth = 600, winHeight = 600; //设置初始化窗口大小
步行者08
2018/10/09
9600
第6章代码-三维造型
本实例参考了著名的Nehe OpenGL示例构建了四棱锥和立方体的实体模型,这两个模型的顶点位置如图6.13所示。可见,四棱锥的四个侧面的顶点序列分别为v0v1v2、v0v2v3、v0v3v4、v0v4v1,底面为v1v2v3v4。传递顶点信息时使用了glVertex3fv函数,以顶点首地址作为参数,比glVertex3f函数直接用顶点坐标作为参数的方式更为方便、直观。在坐标系原点建好的实体可以通过几何变换放置在任意不同的位置。在本示例中,四棱锥被放置在左侧,立方体被放置在右侧。
步行者08
2020/09/21
5360
第6章代码-三维造型
第5章代码-三维观察
目录 5.5 编程实例 5.5.1 二维实例——红蓝三角形 5.5.2 三维实例——立方体透视投影 5.5 编程实例 5.5.1 二维实例——红蓝三角形 #include <GL/glut.h> ty
步行者08
2020/09/19
4900
实验3 OpenGL几何变换
(1)阅读实验原理,运行示范实验代码,掌握OpenGL程序平移、旋转、缩放变换的方法;
步行者08
2018/10/09
1.3K0
4.4.1 二维复合矩阵编程实例
(a)变换前的三角形                 (b)变换后的三角形          (c)程序显示结果
步行者08
2018/10/09
5370
用OpenGL进行曲线、曲面的绘制
实验目的 1)理解Bezier曲线、曲面绘制的基本原理;理解OpenGL中一维、二维插值求值器的用法。 2)掌握OpenGL中曲线、曲面绘图的方法,对比不同参数下的绘图效果差异; 代码1:用四个控制点绘制一条三次Bezier曲线 #include "stdafx.h" #include <stdlib.h> #include <time.h> #include <GL/glut.h> //4个控制点的3D坐标——z坐标全为0 GLfloat ctrlpoints[4][3] = { { -4, -
Zoctopus
2018/06/04
3.3K0
python+opengl显示三维模型小程序 原
已经安装python的系统会自动安装pip,所以只需要一句pip命令就可以安装opengl了,命令如下:
晓歌
2018/08/15
4.3K0
python+opengl显示三维模型小程序
                                                                            原
【C++】OpenGL:创建线段和多边形示例
首先,将main函数中的//glutDisplayFunc(lines); //传递需要勾画的函数取消注释,这是调用线段的操作;
DevFrank
2024/07/24
1780
【C++】OpenGL:创建线段和多边形示例
3.6.2 编程实例-河南地图绘制
#include <iostream> #include <fstream> #include<vector> #include <GL/glut.h> using namespace std;
步行者08
2018/10/09
7990
CG实验6 简单光照与材质
(1) 阅读和修改示范代码中的有关参数,产生不同光照效果,观察显示效果。挑选两张修改的效果图保存为图1-2,与对应修改的代码一起保存至word实验文档中(15分钟);
步行者08
2019/02/25
6820
实验10 Bezier曲线生成-实验提高-交互式生成B样条曲线
本代码通过交互方式来生成三次B样条曲线。主要功能: 根据鼠标左键点击产生控制点,再由控制点生成三次B样条曲线; 鼠标右键弹出菜单“New B-Spline Curve”清除当前曲线,并开始新曲线。 #include <GL/glut.h> #include <stdio.h> #include <stdlib.h> #include <vector> using namespace std; struct Point { int c[2]; int& x = c[0]; int& y = c[1]
步行者08
2022/06/12
6840
实验10 Bezier曲线生成-实验提高-控制点生成B样条曲线
本代码根据已知控制点( 10, 5, 0 ),( 5, 10, 0 ),( -5, 15, 0 ),( -10, -5, 0 ),( 4, -4, 0 ),( 10, 5, 0 ), ( 5, 10, 0 ), ( -5, 15, 0 ), ( -10, -5, 0 ),( 10, 5, 0 )来生成三次B样条曲线。
步行者08
2022/06/12
5910
5.5 Opengl编程实例-红蓝三角形
#include <GL/glut.h> typedef GLfloat point2d[2]; // a point data type void triangle( point2d a, po
步行者08
2018/10/09
7300
实验8 OpenGL交互
(1) 运行示范实验代码1,掌握程序鼠标交互方法,尝试为其添加键盘与菜单控制,实现同样功能;
步行者08
2018/10/09
1.2K0
用OpenGL实现动态的立体时钟
(在学期末做的图形学课程设计,特将学习心得整理如下) 一、设计思路 1,设计一个平面的时钟; 按照 钟面——>中心点——>刻度——>时针——>分针——>秒针 的顺序绘制。 2,利用纹理贴图的知识使平面时钟变成立体的时钟; 3,设置键盘交互; 4,测试,修改,整理代码。 二、部分代码设计 1,键盘交互 void keyboard(unsigned char key, int x, int y) { switch (key) { case 'x': //当按下键盘上d时,以沿X轴旋
Zoctopus
2018/06/04
3.2K0
【C++】OpenGL:freeglut环境配置与基础示例
FreeGLUT(Free OpenGL Utility Toolkit)是一个开源的替代性GLUT库,它提供了类似于GLUT的功能,并在其基础上进行了扩展和改进。FreeGLUT的目标是提供一个跨平台、功能丰富且易于使用的工具库,用于OpenGL程序开发。
DevFrank
2024/07/24
5710
OpenGL光照设置
1.设置光源 (1)光源的种类 环境光 环境光是一种无处不在的光。环境光源放出的光线被认为来自任何方向。因此,当你仅为场景指定环境光时,所有的物体无论法向量如何,都将表现为同样的明暗程度。 点光源 由这种光源放出的光线来自同一点,且方向辐射向四面八方。 平行光 平行光又称镜面光,这种光线是互相平行的。从手电筒、太阳等物体射出的光线都属于平行光。 聚光灯 这种光源的光线从一个锥体中射出,在被照射的物体上产生聚光的效果。使用这种光源需要指定光的射出方向以及锥体的顶角α。 (2)光的成分 对于每一种光源,都有漫射
Zoctopus
2018/06/04
1.2K0
实验6 Bezier曲线生成
了解曲线的生成原理,掌握几种常见的曲线生成算法,利用VC+OpenGL实现Bezier曲线生成算法。
步行者08
2018/10/09
1K0
实验2 基本图元光栅化
(1) 阅读学习所给的直线光栅化的DDA算法示范代码,将其彻底弄懂,根据实验思考题找出其中的错误;同时能在计算机上编译运行,输出正确结果,指出错误并截图保存为图1至word实验文档(30分钟);
步行者08
2019/02/25
1.1K0
实验2 基本图元光栅化
相关推荐
[C++][opengl]利用glut和gluax画矩形等
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验