【数据结构】二叉树(Binary Tree)

文章目录

  • 一、树的概念及结构
  • 二、二叉树的概念及结构
    • 1.二叉树的概念
    • 2.特殊的二叉树
    • 3.二叉树的性质
  • 三、二叉树的存储
    • 顺序存储
    • 链式存储
  • 四、二叉树的实现
    • 1.创建二叉树
    • 2.二叉树的遍历
      • 前序遍历
      • 中序遍历
      • 后序遍历
      • 层序遍历
      • 根据遍历顺序创建二叉树
    • 3.二叉树的基本操作
      • 1.总结点个数
      • 2.二叉树高度
      • 3.第K层结点个数
      • 4.查找
      • 5.判断二叉树是否是完全二叉树
    • 4.销毁二叉树

一、树的概念及结构

是一种非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有层次关系的集合。它看起来像一棵倒挂的树,根朝上,而叶子朝下,所以我们把它叫做为树。
在这里插入图片描述

  • 根结点:没有前驱结点,是当前树中所有结点的祖先。
  • 除根节点外,其余结点被分成一棵结构与树类似的子树。每棵子树的根结点有且只有一个前驱,可以有0个或多个后继。
  • 因此,树是递归定义的。
  • g)

    节点的度:一个节点含有的子树的个数称为该节点的度; 如上图:A的度为3。

    叶子节点:度为0的节点称为叶子节点; 如上图:E、F、G为叶子节点。

    双亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点; 如上图:A是B的父节点,B是E的父节点。

    孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点; 如上图:B是A的子节点,F是D的子节点。

    兄弟节点:具有相同父节点的节点互称为兄弟节点; 如上图:B、C、D是兄弟节点。

    树的度:一棵树中,最大的节点的度称为树的度; 如上图:树的度为3.

    节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推;

    树的高度或深度:树中节点的最大层次/深度; 如上图:树的高度为3

    堂兄弟节点:双亲在同一层的节点互为堂兄弟;如上图:E、F互为堂兄弟节点

    节点的祖先:从根到该节点所经分支上的所有节点;如上图:A是所有节点的祖先

    子孙:以某节点为根的子树中任一节点都称为该节点的子孙。如上图:所有节点都是A的子孙

    森林:由m(m>0)棵互不相交的树的集合称为森林;

    二、二叉树的概念及结构

    1.二叉树的概念

    二叉树是一种特殊的树,每个结点最多只能有两个子结点,即二叉树不存在度大于2的结点。
    二叉树可以分为三个部分:根、左子树、右子树,每颗子树又可以划分为这三个部分,一直递归到叶子结点,叶子结点是其本身的根结点。
    二叉树的子树有左右之分,次序不能颠倒,因此二叉树是有序树。
    在这里插入图片描述

    二叉树有五种基本形态:空树(B的右子树)、只有根节点(D、E、F)、只有左子树(B)、只有右子树、左右子树(A、C)都存在。

    2.特殊的二叉树

    满二叉树:除了叶子结点之外的所有结点都有两个子结点。如果一个满二叉树的层数为K,那么结点总数是20 + 21 + … + 2k-1 = 2k - 1个。

    在这里插入图片描述

    完全二叉树:除了最后一层,完全二叉树的每一层从左到右都是满的。也就是说,如果完全二叉树的层数为 h,那么前 h-1 层一定是一个满二叉树。
    满二叉树是一种特殊的完全二叉树。

    在这里插入图片描述

    斜二叉树:除了叶子结点外,所有结点都只有一个结点,且是一个方向的结点,要么都只有左结点,要么都只有右结点。

    在这里插入图片描述

    3.二叉树的性质

    1.任意二叉树第 i 层最多有2i-1个结点。 ( i ≥ \geq 1)
    2.深度为 h 的任意二叉树的最大结点总数为2h-1。( h ≥ \geq 1)
    3.任意二叉树中,度为0的叶子结点个数永远比度为2的结点个数多一个。
    4.对于一个有n个结点的满二叉树,其深度 h= l o g 2 log_2 log2(n+1)(向上取整)

    三、二叉树的存储

    二叉树一般可以使用两种结构存储,一种顺序结构,一种链式结构。

    顺序存储

    二叉树的顺序结构存储就是使用数组来存储。按照层序遍历的顺序(从上到下,从左到右)依次将结点存储在数组中,如果某个结点的左结点或者右结点不存在,则对应的数组的位置就为空,不存储元素。
    在这里插入图片描述

  • 二叉树顺序存储在物理上是一个数组,在逻辑上是一颗二叉树。
  • 二叉树以数组方式存储,父子结点下标的关系:左结点下标 = 2*父结点下标+1,右结点下标 = 2*父结点下标+2,父结点下标 = (任意孩子结点下标-1)/ 2。
  • 由上图可以看出,如果是非完全二叉树用数组来表示,则会存在空间浪费,所以使用数组一般只适合表示完全二叉树。而对于一般的二叉树,常用链式存储结构。
  • 链式存储

    二叉树的链式存储结构是指,用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。 通常的方法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来存储该结点左孩子和右孩子所在结点的存储地址 。

    在这里插入图片描述

    四、二叉树的实现

    接下来我们用链表来实现二叉树的创建与遍历。

    二叉树定义

    typedef char BTDataType;
    typedef struct BinaryTreeNode
    {
    	BTDataType data;//数据
    	struct BinaryTreeNode* left;//左孩子结点
    	struct BinaryTreeNode* right;//右孩子结点
    }BTNode;
    

    1.创建二叉树

    在这里插入图片描述

    以上图为例建立一棵二叉树。
    以下代码并不是真正创建二叉树的方式,只是为了方便大家理解,所以手动快速创建一棵简单的二叉树。等后面学完二叉树遍历之后,我们再讲解二叉树的真正创建方式。

    //动态申请结点
    BTNode* BuyNode(BTDataType x)
    {
    	BTNode* node = (BTNode*)malloc(sizeof(BTNode));
    	if (NULL == node)
    	{
    		perror("malloc");
    		return NULL;
    	}
    	node->data = x;
    	node->left = node->right = NULL;
    	return node;
    }
    //创建二叉树
    BTNode* CreatTree()
    {
    	BTNode* node1 = BuyNode('A');
    	BTNode* node2 = BuyNode('B');
    	BTNode* node3 = BuyNode('C');
    	BTNode* node4 = BuyNode('D');
    	BTNode* node5 = BuyNode('E');
    	BTNode* node6 = BuyNode('F');
    	node1->left = node2;
    	node1->right = node3;
    	node2->right = node4;
    	node3->left = node5;
    	node4->left = node6;
    	return node1;
    }
    

    2.二叉树的遍历

  • 前面我们讲过,二叉树分为:根、左子树、右子树。每个结点的左右子树又可以划分为这三个部分,直到最后一层的叶子结点,叶子结点是其本身的根结点,左右结点为空。根据这一特点,可以看出,二叉树定义是递归式的。
  • 二叉树遍历是按照某种特定的规则,依次访问每个节点,并且每个节点只访问一次。根据根节点的访问顺序分为三种遍历方式:前序遍历、中序遍历和后序遍历。
  • 在这里插入图片描述

    前序遍历

    前序遍历的访问顺序:根结点、左子树、右子树。
    其中,对于每棵子树,访问顺序也是根结点、左子树、右子树,一直递归到空结点。

    在这里插入图片描述

    在这里插入图片描述相同颜色框的是相同根节点的左右子树

    比如上图的前序遍历访问顺序为:A B NULL D F NULL NULL NULL C E NULL NULL NULL
    而我们一般不打印空结点,所以前序遍历结果为:A B D F C E

    //前序遍历
    void PreOrder(BTNode* root)
    {
    	if (NULL == root)
    	{
    		//printf("NULL ");
    		return;
    	}
    	printf("%c ", root->data);//打印根节点的值
    	PreOrder(root->left);//遍历子树
    	PreOrder(root->right);//遍历右子树
    }
    

    中序遍历

    中序遍历的访问顺序:左子树、根结点、右子树。
    还是拿上图示例,中序遍历访问顺序为:NULL B NULL F NULL D NULL A NULL E NULL C NULL
    去掉空结点后,中序遍历结果为:B F D A E C
    在这里插入图片描述

    在这里插入图片描述

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

    后序遍历

    中序遍历的访问顺序:左子树、右子树、根结点。

    在这里插入图片描述

    在这里插入图片描述上图二叉树的后序遍历访问顺序为:NULL NULL NULL F NULL D B NULL NULL E NULL C A
    去掉空结点后,后序遍历结果为:F D B E C A

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

    层序遍历

    二叉树的层次遍历最好理解,从二叉树的根结点开始,从上到下每一层按照从左到右的顺序遍历。比如上图中二叉树的层序遍历为:A B C D E F

    前面三种遍历都是以递归的形式,而层序遍历可以用队列来实现非递归遍历。前面我们已经用C语言写过队列(点击跳转文章)结构,可以在vs中直接将队列的相关代码copy过来使用,在源文件和头文件中直接添加现有项即可。

    并将队列中的类型改为二叉树类型

    //typedef int QDataType;
    typedef struct BinaryTreeNode* QDataType;
    

    具体过程是:先将当前节点入队列访问当前结点,再将当前结点出队列。然后将当前结点的左结点和右结点按顺序入队,如果为空结点则不用入队,直到队列为空。

    //层序遍历
    void LevelOrder(BTNode* root)
    {
    	assert(root);
    	Queue q;
    	QueueInit(&q);
    	QueuePush(&q, root);
    	while (!QueueEmpty(&q))
    	{
    		BTNode* front = QueueFront(&q);
    		QueuePop(&q);
    		printf("%c ", front->data);
    		if(front->left != NULL)
    			QueuePush(&q, front->left);
    		if (front->right != NULL)
    			QueuePush(&q, front->right);
    	}
    	QueueDestroy(&q);
    	printf("\n");
    }
    

    根据遍历顺序创建二叉树

    数组a存储前序遍历的顺序,比如通过前面我们知道二叉树的前序遍历结果为A B NULL D F NULL NULL NULL C E NULL NULL NULL ;我们将NULL替换成字符#表示空。数组下标用指针接收,否则递归函数中不会改变实参。

    //根据前序遍历的顺序二叉树创建
    BTNode* BTreeCreate_PreOrder(BTDataType* a, int* pi)
    {
    	// 通过前序遍历的数组"AB#DF###CE###"构建二叉树
    	if (a[*pi] == '#')
    	{
    		(*pi)++;//数组下标+1
    		return NULL;
    	}
    	BTNode* node = BuyNode(a[*pi]);//根据数组元素构建二叉树结点
    	(*pi)++;
    	node->left = BTreeCreate_PreOrder(a, pi);
    	node->right = BTreeCreate_PreOrder(a, pi);
    	return node;
    }
    

    根据中序/后序顺序来构建二叉树同理。

    int main()
    {
    	char a[] = "AB#DF###CE###";
    	int i = 0;
    	BTNode* root = BTreeCreate_PreOrder(a, &i);
    	//BTNode* root = BTreeCreate();
    	PreOrder(root);
    	printf("\n");
    	InOrder(root);
    	printf("\n");
    	PostOrder(root);
    	printf("\n");
    	return 0;
    }
    

    3.二叉树的基本操作

    在前面二叉树递归遍历的基础上,我们可以实现一些二叉树的基本操作。

    1.总结点个数

    转换成子问题:二叉树总结点个数=1(根节点个数)+ 左子树的结点个数 + 右子树的节点个数。很容易想到递归。

    //二叉树的总结点个数
    int BTreeSize(BTNode* root)
    {
    	if (NULL == root)
    	{
    		return 0;
    	}
    	return 1 + BTreeSize(root->left) + BTreeSize(root->right);
    }
    

    2.二叉树高度

    当前树的高度 = 左右子树中比较高的高度+1

    //二叉树的高度
    int BTreeLevel(BTNode* root)
    {
    	if (NULL == root)//空结点则返回0
    	{
    		return 0;
    	}
    	int leftLevel = BTreeLevel(root->left);//左子树的高度
    	int rightLevel = BTreeLevel(root->right);//右子树的高度
    	return leftLevel > rightLevel ? leftLevel + 1 : rightLevel + 1;
    }
    

    最好不要写成下面这样,会造成重复递归,判断时遍历左右子树递归两次,返回结果时又递归一次,栈帧开销会很大。

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

    3.第K层结点个数

    求第K层结点个数,转换成子问题:求左子树第K-1层结点个数 + 右子树第K-1层结点个数

    //第k层结点的个数
    int BTreeLevelKSize(BTNode* root, int k)
    {
    	assert(k > 0);//默认根节点为第一层,所以不存在第0层
    	if (NULL == root)//空结点
    	{
    		return 0;
    	}
    	if (1 == k)//遍历到第k层
    	{
    		return 1;
    	}
    	return BTreeLevelKSize(root->left, k-1)+BTreeLevelKSize(root->right, k-1);
    }
    

    4.查找

    //查找值为x的结点
    BTNode* BTreeFind(BTNode* root, BTDataType x)
    {
    	if (NULL == root)
    		return NULL;
    	if (root->data == x)
    		return root;
    
    	BTNode* left = BTreeFind(root->left, x);
    	if (left)//左子树找到则返回
    		return left;
    	//左子树未找到,只可能存在于右子树
    	return BTreeFind(root->right, x);
    }
    

    5.判断二叉树是否是完全二叉树

    完全二叉树按层序走,非空结点一定是连续的。每次将当前结点的左右结点入队列,不用判空,空结点也入队列。遇到第一个空结点时,停止入队,开始判断队列中的元素是否都为空,若出现非空结点,说明不是完全二叉树,反之则是完全二叉树。

    //判断二叉树是否是完全二叉树
    bool BTreeComplete(BTNode* root)
    {
    	assert(root);
    	Queue q;
    	QueueInit(&q);
    	QueuePush(&q, root);
    	while (!QueueEmpty(&q))
    	{
    		BTNode* front = QueueFront(&q);
    		QueuePop(&q);
    		//完全二叉树按层序走,非空结点一定是连续的
    		if (NULL == front)//遍历到第一个空结点则退出循环,进入下个while循环判断
    		{
    			break;
    		}
    		else
    		{
    			//不为空则将左右孩子结点入队
    			QueuePush(&q, front->left);
    			QueuePush(&q, front->right);
    		}
    	}
    	//如果是完全二叉树,则队列中元素全是NULL
    	while (!QueueEmpty(&q))
    	{
    		BTNode* front = QueueFront(&q);
    		QueuePop(&q);
    		//有非空结点则说明非空结点不是完全连续,说明不是完全二叉树
    		if (front != NULL)
    		{
    			QueueDestroy(&q);//返回之前及时释放空间,防止内存泄露
    			return false;
    		}
    	}
    	QueueDestroy(&q);
    	return true;
    }
    

    4.销毁二叉树

    根节点最后释放,否则无法访问左右子树。

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

    点此跳转二叉树完整代码

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

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

相关文章

Vulnhub项目:ICA: 1

1、靶机介绍 靶机地址:ICA: 1 ~ VulnHub 2、渗透过程 首先,部署好靶机后,进行探测,发现靶机ip和本机ip,靶机ip156,本机ip146。 然后查看靶机ip有哪些端口,nmap一下。 出现22、80、3306端口&a…

使用Selenium破解滑动验证码的原理及解决思路

1、获取页面元素信息: 使用Selenium打开目标网页,并通过相关方法获取滑块、背景图等元素的位置和属性信息。可以使用Selenium提供的定位方法(如xpath、CSS选择器等)来找到这些元素。 可以使用find_element_by_xpath或find_elemen…

按键的短按、长按和连续的划分

在实际生活中,我们使用到的按键在短按、长按和按键松开时都会触发不同的功能。按键短按后松开和长按后松开的应用比短按和长按的应用较少,我了解的按键短按后松开和长按后松开的应用是在点动控制和长动控制中。这里主要讨论按键的短按、长按和连续这三种…

基于ConvNeXt网络的图像识别

1、前言 ConvNeXt 网络基于传统的卷积神经网络,与当下 transformer当道而言简直是一股清流 ConvNeXt并没有特别复杂或者创新的结构 ConvNeXt 网络有五种大小,可以参考下面 2、项目实现 完整的项目如下: 这里参考了网上的ConvNeXt 模型&…

计算机服务器中了devicdata勒索病毒如何解密,devicdata勒索病毒解密恢复工具

在网络技术飞速发展的时代,有效地利用网络开展各项工作业务,能够大大提升企业的生产运行效率,改善企业的发展运营模式,但如果网络利用不好就会给企业的数据安全带来严重威胁。近日,云天数据恢复中心接到很多企业的求助…

机柜风扇KTS011温湿度控制器KTO011风机控制温控器机械开关温控仪

品牌:威驰 型号:KTS011常开 产地:中国大陆 颜色分类:KTS011常开,KTO011常闭 KTS011与KTO011的区别 KTS011,常开型,可搭配风扇/风机使用:当环境温度超过温控器设定温度,温控…

如何挑选家用洗地机?需要注意什么?这四款洗地机性价比超高

洗地机结合了扫、拖、吸的功能,一台机器,一个推拉的动作便可以清理干净地面上的干湿垃圾,大大的节省了我们做家务的清洁时间,提升了生活质量。但是面对市面上众多的洗地机型号,我们要怎么去挑选呢,需要主要…

Goland GC

Goland GC 引用Go 1.3 mark and sweep 标记法Go 1.5 三色标记法屏障机制插入屏障删除写屏障总结 Go 1.8 混合写屏障(hybrid write barrier)机制总结 引用 https://zhuanlan.zhihu.com/p/675127867 Garbage Collection,缩写为GC,一种内存管理回收的机制…

JDK1.8 安装并配置环境变量

一、Windows 配置 1 安装文件 jdk-8u401-windows-i586.exe 2 环境变量 JAVA_HOME C:\Program Files (x86)\Java\jdk-1.8 CLASSPATH .;%JAVA_HOME%\lib\tools.jar;%JAVA_HOME%\lib\dt.jar; Path %JAVA_HOME%\bin 说明:Win7/Win8 中 Path 可能需要写成 ;%JAVA_HO…

新能源汽车动力电池浸没式冷却方案介绍与未来趋势

前言 新能源汽车的兴起标志着汽车工业的一次革命,其中动力电池的设计与性能成为了关键。浸没式冷却方案作为一种新兴的技术,为动力电池系统提供了有效的散热解决方案,其在未来的发展趋势备受关注。 一 动力电池浸没式冷却方案介绍 首先&am…

618洗地机推荐,市面上各式各样的洗地机怎么选?这里有答案

洗地机的出现极大地改变了清洁方式,通过结合扫地、拖地、吸尘等多种功能,实现了一机多用的便捷清洁体验。而且洗地机不需要弯腰,每次也不用清洁很长时间,节省出来的时间可以更好的休息,但是市面上各式各样的洗地机怎么…

Amesim基础篇-热仿真常用模型库-Thermal Hydraulic /Resistance

有言在先 流体库、管路库在热管理中是必备模块,如动力电池液冷循环系统均需要Thermal Hydraulic /Resistance库的元件建模。 1 流体物性设置 AMEsim中内嵌了大部分液冷的热物性,直接在流体子模型上选择即可。常规使用的是50%乙二醇水溶液,如…

【小白可懂】SpringBootWeb入门

web开发需要的技术栈: 前端web开发: html css javascript Vue Element Nginx 后端web开发: Maven SpringBoot Web 基础篇 MySOL SpringBoot Mybatis SpringBoot Web开发篇 SpringBoot web进阶篇 什么是spring? 官网&a…

网络爬虫概述与原理

网络爬虫概述与原理 网络爬虫简介狭义上理解功能上理解常见用途总结 网络爬虫分类通用网络爬虫聚焦网络爬虫增量网络爬虫深度网络爬虫 网络爬虫流程网络爬虫采集策略深度有限搜索策略广度优先搜索策略 网络爬虫简介 通过有效地获取网络资源的方式,便是网络爬虫。网…

基于 LlaMA 3 + LangGraph 在windows本地部署大模型 (二)

基于 LlaMA 3 LangGraph 在windows本地部署大模型 (二) #Options local_llm llama3 llm ChatOllama(modellocal_llm, format"json", temperature0) #embeddings #embeddings OllamaEmbeddings(model"nomic-embed-text") embed…

蛋糕店做配送小程序的作用是什么

蛋糕烘焙除了生日需要,对喜吃之人来说往往复购率较高,除线下实体店经营外,更多的商家选择线上多种方式获客转化、持续提高生意营收,而除了进驻第三方平台外,构建品牌私域自营店铺也同样重要。 运用【雨科】平台搭建蛋…

Excel中实现md5加密

1.注意事项 (1)在Microsoft Excel上操作 (2)使用完,建议修改的配置全部还原,防止有风险。 2.准备MD5宏插件 MD5加密宏插件放置到F盘下(直接F盘下,不用放到具体某一个文件夹下) 提示:文件在文章顶部&…

营业执照OCR识别接口如何对接

营业执照OCR识别接口也叫营业执照文字识别OCR接口,指的是传入营业执照图片,精准识别静态营业执照图像上的文字信息。那么营业执照OCR识别接口如何对接呢? 首先我们找到一家有做营业执照OCR识别接口的服务商,数脉API,然后注册账户…

台阶仪测量膜厚原理及优势

台阶仪,也称为探针式轮廓仪或接触式表面轮廓测量仪,主要用于台阶高、膜层厚度、表面粗糙度等微观形貌参数的测量。 台阶仪的工作原理 台阶仪的核心部件是一个精密的触针或探针,它被安装在一个高度可调的支架上。当触针沿被测表面轻轻滑过时…

【免费】WordPress LskyPro0.1.0版本兰空图床插件无法启用修改代码方法

注:启用插件报错,按提示打开main.php文件找到215行代码,错误原因是函数里多了一个,号,应该是忘记去掉了,把,号去掉就可以了 目录 项目介绍功能计划功能快速入门相关文章: 项目介绍 此项目为通过…