数据结构-链式二叉树

文章目录

  • 一、链式二叉树
    • 1.1 链式二叉树的创建
    • 1.2 根、左子树、右子树
    • 1.3 二叉树的前中后序遍历
      • 1.3.1前(先)序遍历
      • 1.3.2中序遍历
      • 1.3.3后序遍历
    • 1.4 二叉树的节点个数
    • 1.5 二叉树的叶子结点个数
    • 1.6 第K层节点个数
    • 1.7 二叉树的高度
    • 1.8 查找指定的值(val)
    • 1.9 二叉树的销毁
  • 二、层序遍历
    • 2.1 二叉树的层序遍历
    • 2.2 层序遍历判断二叉树是否是完全二叉树
  • 三、二叉树的性质(补充)
    • 3.1选择题

一、链式二叉树

因为二叉树的度是确定的,所以可以用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。通常的方法是链表中每个结点由三个域组成: 数据域和左右指针域。左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址,其结构如下:

typedef int BTDataType;
//二叉链
typedef struct BinaryTreeNode
{
	struct BinaryTreeNode* left; //指向当前结点的左孩子
	struct BinaryTreeNode* right; //指向当前结点的右孩子
	BTDataType val; //当前结点的值域
}BTNode;

1.1 链式二叉树的创建

二叉树的创建方式比较复杂, 为了更好的步入到二叉树的内容中,我们先手动创建一棵链式二叉树。

比如我们要创建如下一个二叉树:
在这里插入图片描述

//创建节点
BTNode* BuyBTNode(int val)
{
	BTNode* newnode = (BTNode*)malloc(sizeof(BTNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return NULL;
	}
	newnode->val = val;
	newnode->left = NULL;
	newnode->right = NULL;
	return newnode;
}
//创建链式二叉树
BTNode* CreateBTree()
{
	BTNode* node1 = BuyBTNode(1);
	BTNode* node2 = BuyBTNode(2);
	BTNode* node3 = BuyBTNode(3);
	BTNode* node4 = BuyBTNode(4);
	BTNode* node5 = BuyBTNode(5);
	BTNode* node6 = BuyBTNode(6);
	BTNode* node7 = BuyBTNode(7);
	
	node1->left = node2;
	node1->right = node4;
	node2->left = node3;
	node4->left = node5;
	node4->right = node6;
	node5->left = node7;
	
	return node1;
}

1.2 根、左子树、右子树

由于这里是链式二叉树,所以后面我们看到一个二叉树,就要把树分成三个部分:根、左子树、右子树。回顾二叉树的概念:二叉树分为空树和非空二叉树,非空二叉树由根结点、根结点的左子树、根结点的右子树组成的。
在这里插入图片描述根结点的左子树和右子树分别又是由子树的根结点、子树结点的左子树、子树结点的右子树组成的。因此二叉树的定义是递归式的, 后序链式二叉树的操作中基本都是按照该概念实现的。

1.3 二叉树的前中后序遍历

二叉树的操作离不开树的遍历,那二叉树的遍历有哪些方式呢?比如我们要遍历下面这个二叉树:
在这里插入图片描述二叉树的遍历分为三种:前/中/后序遍历。下面我们分别讲解三种遍历。

1.3.1前(先)序遍历

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

访问顺序为: 根结点、左子树、右子树

void PreOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("# ");
		return;
	}
	
	printf("%c ",root->val);
	PreOrder(root->left);
	PreOrder(root->right);
}

先看运行结果:
在这里插入图片描述
在这里插入图片描述打印的结果为什么是上面的样子呢?这里涉及到函数的递归,所以要先理解前序遍历的顺序是根、左子树、右子树。也就是从根节点开始遍历,根遍历完了就到左子树,那左子树又可以分为根、左子树、右子树,又会按照根左右的顺序遍历;要等到左子树完完全全遍历完了,才会遍历到右子树。这里的难点就是: 理解递归递推的终止条件和每一层函数调用完毕后会返回上一层调用它的函数处继续往后执行(回归)。

前序遍历的递归过程如下:
在这里插入图片描述

1.3.2中序遍历

◆中序遍历(lnorder Traversal(LDR)): 访问根结点的操作发生在遍历其左右子树之中(间)

访问顺序为: 左子树、根结点、右子树

void InOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("# ");
		return;
	}

	InOrder(root->left);
	printf("%c ", root->val);
	InOrder(root->right);
}

打印结果如下:
在这里插入图片描述
在这里插入图片描述中序遍历就是从左子树开始遍历,左子树遍历完了就遍历根,最后再遍历右子树。而其中的子树又可以分为左子树、根、右子树,又会按照左根右的顺序遍历。

1.3.3后序遍历

▼后序遍历(Postorder Traversal(LRD)): 访问根结点的操作发生在遍历其左右子树之后

访问顺序为: 左子树、右子树、根结点

void PostOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("# ");
		return;
	}

	PostOrder(root->left);
	PostOrder(root->right);
	printf("%c ", root->val);
}

打印结果如下:
在这里插入图片描述
在这里插入图片描述后序遍历就是根要在左右子树之后访问。前\中\后续遍在历代码中的递归调用虽然只是换了个位置,但是递归调用的过程是不相同的,如不理解函数的逐层调用过程,就需要画图层层递进地来深刻剖析递归。

1.4 二叉树的节点个数

通过递归计算二叉树的节点个数:

int BTreeSize(BTNode* root)
{
	if (root == NULL)
		return 0;

	return BTreeSize(root->left) 
	     + BTreeSize(root->right) + 1;
}

这里要理解递归的过程是什么样, 以下图的举例来理解这里的递归:
在这里插入图片描述这里递推的结束条件是:子树为空才会回归上一层; 而空树就没有节点嘛!所以return 0。在返回上一层的时候可以看到是左右子树都递归完毕而且还要加一个1之后才返回上一层,加的1其实就是加上根节点的数量。

1.5 二叉树的叶子结点个数

通过递归计算二叉树的叶子节点个数:

int BTreeLeafSize(BTNode* root)
{
	if (root == NULL)
		return 0;

	if (root->left == NULL && root->right == NULL)
		return 1;

	return BTreeLeafSize(root->left) 
	     + BTreeLeafSize(root->right);
}

没有孩子节点的节点就是叶子节点,所以递推结束的条件就是:该节点的左右子树为空就返回1,即把该叶子节点的数量计上。而一开始的判空其实针对对根节点而言的。如果树都为空,那就没有叶子节点啦。举例递归过程:
在这里插入图片描述

1.6 第K层节点个数

二叉树的第K层节点个数:

int BTreeLevelKSize(BTNode* root, int k)
{
	assert(k > 0);
	if (root == NULL)
		return 0;

	if (k == 1)
		return 1;

	return BTreeLevelKSize(root->left, k - 1)
		 + BTreeLevelKSize(root->right, k - 1);
}

计算二叉树第K层的节点,这里需要加入一个参数k才能实现,因为我们不知道递归什么时候会到达第k层,所以传一个参数k,让它每递推一次就递减1,若根节点为第一层,则k递减到1就到达第k层了(递推终止条件)。当然还有一个递推终止条件就是还没到达第k层就已经出现了空节点,所以还没到达第k层时就遇到了空就返回。
在这里插入图片描述

1.7 二叉树的高度

通过递归计算二叉树的高度/深度:

int BTreeDepth(BTNode* root)
{
	if (root == NULL)
		return 0;

	int leftDepth = BTreeDepth(root->left);
	int rightDepth = BTreeDepth(root->right);

	return leftDepth > rightDepth ? leftDepth + 1 : rightDepth + 1;
}

计算二叉树的高度也就是计算该二叉树有多少层?那就是我们要找到一个沿着其祖先路径最长的那个节点。所以左右子树之间需要通过比较返回较长的那个节点才行。每返回一层要加个1因为每个节点相较于其孩子节点都是一个根,只要不为空就要算一节长度。
在这里插入图片描述注意:如果上面的代码简化写成下面的样子:

return BTreeDepth(root->left) > BTreeDepth(root->right)
 ? BTreeDepth(root->left)+1 : BTreeDepth(root->right)+1;

这样写时间效率上会非常差。试想:如果二叉树的节点数量庞大,即层数很多时;左右子树相比较就要进行两个函数的递归调用,递归调用又是三目表达式,那时间消耗就会很大;而等比较出了大小以后,才决定三目表达式的返回值;而返回值又是一个函数的递归调用,递推进入下一层以后还是面临同样的三目表达式,同样的事情一遍又一遍地重复,就会消耗非常非常多的时间。所以这里不建议这么写,最好的方式就是将函数的返回值记录下来,这样回归的时候将记录下来的值返回去,就不会造成上面的情况。

1.8 查找指定的值(val)

在二叉树中查找指定的值x, 如果找到则返回该节点的地址, 否则返回NULL。

BTNode* BTreeFind(BTNode* root, BTDataType x)
{
	if (root == NULL)
		return NULL;
	if (root->val == x)
		return root;

	BTNode* ret1 = BTreeFind(root->left, x);
	if (ret1 != NULL)
		return ret1;

	return BTreeFind(root->right, x);
}

举如下一个例子:比如我们要找x == 3的节点,则函数的递归过程如下:
在这里插入图片描述递归的难点就是返回值不是一下子返回到最上面(最外面)的函数,而是返回上一层调用它的函数处。

1.9 二叉树的销毁

销毁二叉树的销毁就比较简单了, 递归的过程采用后续遍历释放节点:

void BTreeDestroy(BTNode* root)
{
	if (root == NULL)
		return;

	BTreeDestroy(root->left);
	BTreeDestroy(root->right);
	free(root);
}

链式二叉树适合于数据的存储,但并不适用于数据的增删查改。所以这里只是了解链式二叉树的性质,以及怎样通过递归去求二叉树的节点、叶子结点、高度等。

二、层序遍历

2.1 二叉树的层序遍历

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

实现层序遍历最好的方法是借助数据结构: 队列

在这里插入图片描述可以看到上图通过队列就可以实现二叉树的层序遍历,每出队一个元素就让该元素的左右孩子入队。代码实现如下:(队列的性质是先进先出)

void LevelOrder(BTNode* root)
{
	Queue q;
	QueueInit(&q);
	if (root != NULL)
		QueuePush(&q, root);

	while (!QueueEmpty(&q))
	{
		BTNode* top = QueueFront(&q);
		printf("%c ", top->val);
		QueuePop(&q);
		if (top->left != NULL)
		{
			QueuePush(&q, top->left);
		}
		if (top->right != NULL)
		{
			QueuePush(&q, top->right);
		}
	}
	QueueDestroy(&q);
}

注意:这里队列中每个节点存放的值(data)是二叉树节点的地址。所以队列出队只是将队列的头结点释放掉了,而并没有把节点里存放的二叉树节点指针删掉。上面的代码中可以看到已经提前将队头节点的值(二叉树节点的指针)存放在top里面了。

2.2 层序遍历判断二叉树是否是完全二叉树

有了层序遍历这种方法,我们其实就可以用此方法判断一个二叉树是否是完全二叉树:
在这里插入图片描述在这里插入图片描述

bool IsCompleteBTree(BTNode* root)
{
	Queue q;
	QueueInit(&q);
	if (root != NULL)
		QueuePush(&q, root);

	while (!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);
		QueuePop(&q);
		//遇到有空指针的地方就跳出循环
		if (front == NULL)
			break;

		QueuePush(&q,front->left);
		QueuePush(&q,front->right);
	}
	//只管出队 
	//在出队的过程中若有不为空的指针说明不是完全二叉树
	while (!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);
		QueuePop(&q);
		
		if (front != NULL)
		{
			QueueDestroy(&q);
			return false;
		}
	}
	QueueDestroy(&q);
	return true;
}

三、二叉树的性质(补充)

1.若规定根结点所在层数为1, 则一棵非空二叉树的第i层上最多有2i-1个结点.
2.若规定根结点所在层数为1,则深度(层数)为h的二叉树的最大结点数是2h-1
3.对任何一棵二叉树, 如果度为0的叶结点个数为n0, 度为2的分支结点个数为n2, 则有: n0=n2+1

解释一下第三个性质:
在这里插入图片描述在这里插入图片描述

3.1选择题

(1) 某完全二叉树按层次输出(同一层从左到右)的列为ABCDEFGH,该完全二叉树的前序序列为 (A)
A ABDHECFG
B ABCDEFGH
C HDBEAFCG
D HDEBFGCA

(2) 某二叉树的先序遍历和中序遍历如下: 先序遍历: EFHIGJK; 中序遍历: HFIEJKG. 则二叉树根节点为 (A)
A E
B F
C G
D H

(3) 一棵二叉树的中序遍历序列为: badce,后序遍历序列为: bdeca,则该二叉树的前序历列为 (D)
A adbce
8 decab
C debac
D abcde
记住:前序序列能确定根节点在最左边,后续遍历能确定根节点在最右边,再结合中序遍历就能分割出整体的左、根、右。将该二叉树画出来。

(4) 某二叉树的后序遍历序列与中序遍历序列相同,均为ABCDEF,则按层次输出(同一层从左到右)为 (A)
A FEDCBA
B CBAFED
C DEFCBA
D ABCDEF

(5) 某二叉树共有399个结点,其中有199个度为2的结点,则该二又树中的叶子结点数为 (B)
A 不存在这样的二叉柯
B 200
C 198
D 199
通过二叉树性质可知:该二叉树的叶子节点个数为199+1 == 200

(6) 下列数据结构中,不适合采用顺序存储结构的是 (AC)
A 非完全二叉树
B 堆
C 队列
D 栈

(7) 在具有2n个结点的完全二叉树中中,叶子结点个数为(A)
A n
B n+1
C n-1
D n/2

在这里插入图片描述

(8) 一棵完全二又树的结点个数为531个,那么这棵树的高度为 (B)
A 11
B 10
C 8
D 12

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

游戏引擎学习第99天

仓库:https://gitee.com/mrxiao_com/2d_game_2 黑板:制作一些光场(Light Field) 当前的目标是为游戏添加光照系统,并已完成了法线映射(normal maps)的管道,但还没有创建可以供这些正常映射采样的光场。为了继续推进&…

LSTM变种模型

GRU GRU简介 门控循环神经网络 (Gated Recurrent Neural Network,GRNN) 的提出,旨在更好地捕捉时间序列中时间步距离较大的依赖关系。它通过可学习的门来控制信息的流动。其中,门控循环单元 (Gated Recurrent Unit , GRU) 是…

业务开发 | 基础知识 | Maven 快速入门

Maven 快速入门 1.Maven 全面概述 Apache Maven 是一种软件项目管理和理解工具。基于项目对象模型的概念(POM),Maven 可以从中央信息中管理项目的构建,报告和文档。 2.Maven 基本功能 因此实际上 Maven 的基本功能就是作为 Ja…

新一代SCADA: 宏集Panorama Suite 2025 正式发布,提供更灵活、符合人体工学且安全的应用体验

宏集科技宣布正式推出全新Panorama Suite 2025 SCADA软件!全新版本标志着 Panorama Suite的一个重要里程碑,代表了从 Panorama Suite 2022 开始并跨越三个版本(2022、2023、2025)的开发过程的顶峰。 此次重大发布集中在六个核心主…

PAT乙级真题 — 1080 MOOC期终成绩(java)【测试点3超时】

对于在中国大学MOOC(http://www.icourse163.org/ )学习“数据结构”课程的学生,想要获得一张合格证书,必须首先获得不少于200分的在线编程作业分,然后总评获得不少于60分(满分100)。总评成绩的计…

【Oracle篇】浅谈执行计划中的多表连接(含内连接、外连接、半连接、反连接、笛卡尔连接五种连接方式和嵌套、哈希、排序合并三种连接算法)

💫《博主介绍》:✨又是一天没白过,我是奈斯,从事IT领域✨ 💫《擅长领域》:✌️擅长阿里云AnalyticDB for MySQL(分布式数据仓库)、Oracle、MySQL、Linux、prometheus监控;并对SQLserver、NoSQL(…

TCP 端口号为何位于首部前四个字节?协议设计的智慧与启示

知乎的一个问题很有意思:“为什么在TCP首部中要把TCP的端口号放入最开始的四个字节?” 这种问题很适合我这种搞历史的人,大年初一我给出了一个简短的解释,但仔细探究这个问题,我们将会获得 TCP/IP 被定义的过程。 文…

oracle表分区--范围分区

文章目录 oracle表分区分区的原因分区的优势oracle表分区的作用oracle表分区类型一、范围分区二、 创建分区表和使用:1、按照数值范围划分2、按照时间范围3、MAXVALUE2. 向现有表添加新的分区3、 分区维护和重新组织(合并/删除) oracle表分区…

蓝桥杯(B组)-每日一题(求最大公约数最小公倍数)

题目&#xff1a; 代码展现&#xff1a; #include<iostream> using namespace std; int main() {int m,n,x,y;cin>>m>>n;//输入两个整数int b;bm%n;//取余数xm;//赋值yn;while(b)//当余数不为0的时候{xy;//辗转相除求最小公约数yb;bx%y;}cout<<y<&…

基于STM32的学习环境控制系统设计

&#x1f91e;&#x1f91e;大家好&#xff0c;这里是5132单片机毕设设计项目分享&#xff0c;今天给大家分享的是学习环境控制。 设备的详细功能见网盘中的文章《21、基于STM32的学习环境控制系统设计》&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1uWSZX2zbZwy9sY…

WPS接入DeepSeek模型

1.wps 下载安装 WPS-支持多人在线协作编辑Word、Excel和PPT文档_WPS官方网站 &#xff08;最好是安装最新的wps&#xff09; 2.offieceAi工具下载安装 软件下载 | OfficeAI助手 下载后安装下载下来的两个工具。安装路径可以自行修改 3.打开WPS,点击文件-》 选项-》信任中心 勾…

4. React 中的 CSS

用例中的干净的脚手架的创建可以参考另一篇文章&#xff1a;3.React 组件化开发React官方并没有给出在React中统一的样式风格&#xff1a; 由此&#xff0c;从普通的css&#xff0c;到css modules&#xff0c;再到css in js&#xff0c;有几十种不同的解决方案&#xff0c;上百…

Unity进阶教程AOI算法原理详解

最新课程《全栈双客户端(Unity/Cocos) TurnKey方案》更新了AOI专题&#xff0c;今天分享一下AOI算法的实现原理。 AOI的功能和作用 在MMORPG网路游戏当中&#xff0c;单服同时在线一般都会有几千人。当有个玩家执行一个操作&#xff0c;理想情况下要把玩家的操作广播同步给单…

w~大模型~合集30

我自己的原文哦~ https://blog.51cto.com/whaosoft/13284996 #VideoMamba 视频理解因大量时空冗余和复杂时空依赖&#xff0c;同时克服两个问题难度巨大&#xff0c;CNN 和 Transformer 及 Uniformer 都难以胜任&#xff0c;Mamba 是个好思路&#xff0c;让我们看看本文是…

【ThreeJS Basics 1-3】Hello ThreeJS,实现第一个场景

文章目录 环境创建一个项目安装依赖基础 Web 页面概念解释编写代码运行项目 环境 我的环境是 node version 22 创建一个项目 首先&#xff0c;新建一个空的文件夹&#xff0c;然后 npm init -y , 此时会快速生成好默认的 package.json 安装依赖 在新建的项目下用 npm 安装依…

Python----PyQt开发(PyQt基础,环境搭建,Pycharm中PyQttools工具配置,第一个PyQt程序)

一、QT与PyQT的概念和特点 1.1、QT QT是一个1991年由The Qt Company开发的跨平台C图形用户界面应用程序开发 框架&#xff0c;可构建高性能的桌面、移动及Web应用程序。也可用于开发非GUI程序&#xff0c;比如 控制台工具和服务器。Qt是面向对象的框架&#xff0c;使用特殊的代…

PostgreSQL 开发利器:Navicat 核心功能与资源攻略

近几年&#xff0c;&#x1f418; PostgreSQL 在全球数据库排名中表现优异。在 2025 年 2 月 DB-Engines 排名中 (如图)&#xff0c;PostgreSQL 稳居第四名&#xff0c;并逐渐逼近第三名的 Microsoft SQL Server&#xff0c;其评分和受欢迎度持续增长&#xff0c;成为开源数据库…

大模型数据集全面整理:444个数据集下载地址

本文针对Datasets for Large Language Models: A Comprehensive Survey 中的 444 个数据集&#xff08;涵盖8种语言类别和32个领域&#xff09;进行完整下载地址整理收集。 2024-02-28&#xff0c;由杨刘、曹家欢、刘崇宇、丁凯、金连文等作者编写&#xff0c;深入探讨了大型语…

【AI大模型】Ollama部署本地大模型DeepSeek-R1,交互界面Open-WebUI,RagFlow构建私有知识库

文章目录 DeepSeek介绍公司背景核心技术产品与服务应用场景优势与特点访问与体验各个DeepSeek-R系列模型的硬件需求和适用场景 Ollama主要特点优势应用场景安装和使用配置环境变量总结 安装open-webui下载和安装docker desktop配置镜像源安装open-webui运行和使用 RagFlow介绍主…

修改docker内容器中的某配置文件的命令

先找到配置文件config.php find / -name "config.php" 2>/dev/null 然后用vi编辑器修改配置文件 vi /var/www/config.php 最后就是vi的基本操作&#xff0c;根据具体需求使用&#xff1a; vi 有两种主要模式&#xff1a; 命令模式&#xff1a;进入 vi 后的默认…