【数据结构】二叉树详解(上篇)

news2024/11/18 6:17:38

🧑‍💻作者: @情话0.0
📝专栏:《数据结构》
👦个人简介:一名双非编程菜鸟,在这里分享自己的编程学习笔记,欢迎大家的指正与点赞,谢谢!

在这里插入图片描述

二叉树(上)

  • 前言
  • 一、树的基本概念
    • 1.树的定义
    • 2.树的基本术语
    • 3.树的表示
  • 二、二叉树
    • 1.二叉树的定义及主要性质
      • 1.1二叉树的定义
      • 1.2几个特殊的二叉树
      • 1.3二叉树的性质
    • 2.二叉树的存储结构
      • 2.1顺序存储结构
      • 2.2链式存储结构
  • 三、二叉树的顺序存储实现
    • 1.堆的概念及结构
    • 2. 堆的实现
      • 2.1 堆向下调整算法
      • 2.2 堆的创建
      • 2.3 堆的插入
      • 2.4 堆的删除
  • 总结


前言

  本文将主要对树、二叉树的相关概念和性质展开讲解,同时用代码实现一种特殊的二叉树(堆)的顺序存储实现。


一、树的基本概念

1.树的定义

  树是一种非线性的数据结构,它是由 n(n >= 0)个有限结点组成一个具有层次关系的集合。当 n = 0 时,称为空树
   ① 有且仅有一个特殊的点称为根结点,根结点没有前驱结点;
   ② 当 n > 1 时,其余结点可分为 m(m > 0)个互不相交的有限集 T1、T2、……、Tm,其中每个集合本身又是一棵树,并且称为根的子树。
很明显,树的定义是递归的,即再树的定义中又用到了其自身,树是一种递归的数据结构。树作为一种逻辑结构,同时也是一种分层结构,具有以下两个特点:
   ① 树的根结点没有前驱,除根结点外的所有的结点有且只有一个前驱结点;
   ② 树中所有结点可以有零个或多个后继结点。
注意: 树适合于表示具有层次结构的数据。树中的某个结点(除根结点外)最多只和上一层的一个结点(其父节点)有直接关系,根结点没有直接上层结点,因此在 n 个结点的树中有 n-1 条边。而树中的每个结点与其下一层的零个或多个节点有直接关系。也就是说,树形结构中,子树之间不能有交集,否则就不是树形结构。

在这里插入图片描述

2.树的基本术语

在这里插入图片描述

结点的度:树中一个结点的孩子个数被称为该结点的度,如上图:A结点的度为6;
树的度:树中结点最大的度数称为树的度,如上图:树的度为6;
叶子结点:树中度为0的结点被称为叶子结点(或者终端结点),如上图: B C H I P Q K L M N都为叶子结点;
分支节点:度大于0的结点被称为分支节点(或者非终端结点),如上图: D E J F G;
孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点; 如上图:B是A的孩子节点
兄弟节点:具有相同父节点的节点互称为兄弟节点; 如上图:B、C是兄弟节点
树的高度、深度:结点的层次从树根开始定义,根结点在第一层,它的子结点在第二层,以此类推。结点的高度是从叶结点开始自底向上逐层累加的;结点的深度是从根结点开始自顶向下逐层累加的。树的高度(或深度)是树中结点的最大层数,如上图树的高度为 4。
堂兄弟节点:双亲在同一层的结点互为堂兄弟,如上图:H、I互为兄弟结点。
结点的祖先:从根到该结点所经分支上的所有结点,如上图:A是所有结点的祖先。
子孙:以某结点为根的子树中任一结点都称为该结点的子孙。如上图:所有结点都是A的子孙。
森林:由m(m>0)棵互不相交的树的集合称为森林;

3.树的表示

  树结构相对线性表就比较复杂了,要存储表示起来就比较麻烦了,既要保存值域,也要保存结点和结点之间的关系,实际中树有很多种表示方式如:双亲表示法,孩子表示法、孩子双亲表示法以及孩子兄弟表示法等。我们这里就简单的了解其中最常用的孩子兄弟表示法。

typedef int DataType;
struct Node
{
	struct Node* _firstChild1; // 第一个孩子结点
	struct Node* _pNextBrother; // 指向其下一个兄弟结点
	DataType _data; // 结点中的数据域
};

在这里插入图片描述

二、二叉树

1.二叉树的定义及主要性质

1.1二叉树的定义

  二叉树是一种特殊的树,其特点就是每个结点最多有两棵子树(即二叉树中不存在度大于2的结点),二叉树是 n (n>=0)个结点的有限集合:
  ①:或者为空二叉树,即 n = 0;
  ②:或者由一个根结点和两个互不相交的被称为根的左子树和右子树组成,左子树和右子树有分别是一棵二叉树。
  二叉树是有序树,若将其左右子树颠倒,则成为另一棵不同的二叉树。即树中结点只有一棵子树,也要区分它是左子树还是右子树。对于任意的二叉树都是由以下几种情况复合而成的:
在这里插入图片描述
  二叉树与度为 2 的有序树的区别:
  ①:度为 2 的树至少有 3 个结点,而二叉树可以为空;
  ②:度为 2 的有序树的孩子的左右次序是相对于另一个孩子而言的,若某个结点只有一个孩子,则这个孩子就无需区分其左右次序,而二叉树无论孩子数是否为 2 ,均需要确定其左右次序,即二叉树的结点次序不是相对于另一个结点而言的,而是确定的。

1.2几个特殊的二叉树

  1)满二叉树:一个二叉树如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为K,且结点总数是 (2^k) - 1 ,则它就是满二叉树。

  2)完全二叉树:完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树(也就是说,前 k-1 层是满的,第 k 层从左到右的叶子结点是连续的)。 要注意的是满二叉树是一种特殊的完全二叉树。
在这里插入图片描述

1.3二叉树的性质

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

证明:设度为0,1和2的结点个数分别为n0,n1和n2,结点总数n=n0+n1+n2。再看二叉树中的分支数,除根结点外,其余结点都有一个分支进入,设B为分支总数,则n=B+1。由于这些分支是由度为1或2的结点射出的,所以又有B=n1+2n2。于是得n0+n1+n2=n1+2n2+1,则n0=n2+1。

  1. 若规定根节点的层数为1,具有n个结点的满二叉树的深度,h= log2(n+1);
  2. 对于具有n个结点的完全二叉树,如果按照从上至下从左至右的数组顺序对所有节点从0开始编号,则对于序号为i的结点有:
      ①:若 i > 0,i 位置节点的双亲序号:(i-1)/2;i=0,i为根节点编号,无双亲节点;
      ②:若 2i+1 < n,左孩子序号:2i+1;若 2i+1>=n否则无左孩子;
      ③:若 2i+2 < n,右孩子序号:2i+2;若 2i+2>=n否则无右孩子。

2.二叉树的存储结构

2.1顺序存储结构

  二叉树的顺序存储是指用一组地址连续的存储单元依次自上而下、自左而右存储二叉树上的结点。根据二叉树的性质,完全二叉树和满二叉树采用顺序存储比较合适,树中结点的序号可以唯一的反映结点之间的逻辑关系,这样既能最大可能节省存储空间,又能利用数组元素的下标值确定结点在二叉树的位置,以及结点之间的关系。但对于一般的二叉树,为了让数组下标能反映出二叉树中结点之间的逻辑关系,只能添加一些并不存在的空结点,让其每个结点与完全二叉树上的结点相对照,再存储到一维数组的相应分量中。
在这里插入图片描述

2.2链式存储结构

  因为顺序存储的空间利用率较低,因此二叉树一般采用链式存储结构,二叉树的链式存储结构是指用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。 通常的方法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址 。

在这里插入图片描述

typedef int BTDataType;
// 二叉链
struct BinaryTreeNode
{
	struct BinTreeNode* lchild; // 指向当前节点左孩子
	struct BinTreeNode* rchild; // 指向当前节点右孩子
	BTDataType data; // 当前节点值域
};
 
// 三叉链
struct BinaryTreeNode
{
	struct BinTreeNode* Parent; // 指向当前节点的双亲
	struct BinTreeNode* _lchild; // 指向当前节点左孩子
	struct BinTreeNode* _rchild; // 指向当前节点右孩子
	BTDataType data; // 当前节点值域
};

三、二叉树的顺序存储实现

1.堆的概念及结构

  普通的二叉树是不适合用数组来存储的,因为可能会存在大量的空间浪费。而完全二叉树更适合使用顺序结构存储。现实中我们通常把堆(一种二叉树)使用顺序结构的数组来存储。
  对于一个连续数组,把它的所有元素按完全二叉树的顺序存储方式存储在一个一维数组中,并满足所有的父亲结点都大于(小于)它的孩子结点,则称为大堆(或小堆)。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。

堆的性质:
堆中某个节点的值总是不大于或不小于其父节点的值;
堆总是一棵完全二叉树。

2. 堆的实现

2.1 堆向下调整算法

  假若现在给你一个数组,逻辑上看做一颗完全二叉树。我们通过从根节点开始的向下调整算法可以把它调整成一个小堆。向下调整算法有一个前提:左右子树必须是一个堆,才能调整

int array[] = {27,15,19,18,28,34,65,49,25,37};

在这里插入图片描述

void Swap(HPDataType* left, HPDataType* right)
{
	HPDataType temp = *left;
	*left = *right;
	*right = temp;
}
void AdjustDown(Heap* hp,int n,int parent)
{
	int child = parent * 2 + 1; //调整结点的左孩子
	while (child < n) //当child大于结点个数时调整完毕
	{
		//判断是否有右孩子并且右孩子大于左孩子
		if (child + 1 < n&&hp->array[child] < hp->array[child + 1])
		{
			child += 1;
		}
		if (hp->array[child]>hp->array[parent])
		{
			Swap(&hp->array[child], &hp->array[parent]); //若孩子大于父亲则交换
			//继续向下调整继续判断
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			return;
		}
	}
}

2.2 堆的创建

  对于一个非堆的完全二叉树,我们要将其转换为一个堆就要从倒数第一个非叶子节点开始执行堆向下调整算法,一直调整直到根结点 ,这样这棵树就成了一个堆。

void HeapCreate(Heap* hp, HPDataType* a, int n)
{
	assert(hp);
	hp->array = (HPDataType*)malloc(sizeof(HPDataType)*n);
	if (hp->array == NULL)
	{
		return;
	}
	//将一个数组全部拷贝到要调整的二叉树中
	memcpy(hp->array, a, sizeof(HPDataType)*n);
	hp->size = hp->capacity = n;
	//从倒数第一个非叶子节点开始调整
	for (int root = (n - 2) / 2; root >= 0; root--)
	{
		AdjustDown(hp, n, root);
	}
}

2.3 堆的插入

  先将待插入元素放置在数组的最后,也就是二叉树的最后一个叶子结点,然后再执行向上调整算法。而向上调整算法就是将刚插入的结点作为孩子,再找到它的父亲结点与之比较,若孩子结点大于父亲结点就交换,一直到根结点或者孩子结点小于父亲结点才结束。

void AdjustUP(Heap* hp, int child)
{
	assert(hp);
	int parent = (child - 1) / 2;
	while (child)
	{
		if (hp->array[child]>hp->array[parent])
		{
			Swap(&hp->array[child], &hp->array[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			return;
		}
	}
}

void CheckCapacity(Heap* hp)
{
	assert(hp);
	if (hp->capacity == hp->size)
	{
		int newCapacity = hp->capacity + 3;
		HPDataType* temp = (HPDataType*)realloc(hp->array,sizeof(HPDataType)* newCapacity);
		if (NULL == temp)
		{
			assert(0);
			return;
		}
		hp->array = temp;
		hp->capacity = newCapacity;
	}
}

void HeapPush(Heap* hp, HPDataType x)
{
	assert(hp);
	CheckCapacity(hp); //先检查空间是否已满,未满再插入,已满先扩容再插入
	hp->array[hp->size++] = x;
	AdjustUP(hp, hp->size - 1); //想上调整
}

2.4 堆的删除

  首先将根结点和最后一个叶子结点交换,然后将二叉树的结点个数减 1 ,这就完成了根节点的删除,但这是二叉树已不是一个堆,问题就出现刚刚交换上去的根结点,所以只需对根节点完成向下调整算法即可。

void HeapPop(Heap* hp)
{
	assert(hp);
	Swap(&hp->array[0], &hp->array[hp->size-1]); //先交换
	hp->size--; //再删除
	AdjustDown(hp, hp->size, 0);
}

总结

  本文主要针对于二叉树的理解、堆的创建和对堆的一系列操作。重点在于二叉树的相关性质以及完全二叉树的理解,同时一定对堆的向下调整算法理解清楚。当然,二叉树的相关知识点还未完结,后续会继续总结。

  感谢您的阅读,若文章存在问题还烦请指出,感觉有帮到你的话还请一键三连。

在这里插入图片描述

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

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

相关文章

18 【Redux Toolkit】

18 【Redux Toolkit】 上边的案例我们一直在使用Redux核心库来使用Redux&#xff0c;除了Redux核心库外Redux还为我们提供了一种使用Redux的方式——Redux Toolkit。它的名字起的非常直白&#xff0c;Redux工具包&#xff0c;简称RTK。RTK可以帮助我们处理使用Redux过程中的重…

ABTest样本量计算

A/B 测试一般是比较实验组和对照组在某些指标上是否存在差异&#xff0c;当然更多时候是看实验组相比对照组某个指标表现是否更好。 这样的对比在统计学上叫做两样本假设检验&#xff0c;即实验组和对照组为两样本&#xff0c;假设检验的原假设Ho&#xff1a;实验组和对照组无…

Springboot 整合 JWT + Redis 实现双Token 校验Demo(简单实现)

一、新建一个SpringBoot 项目&#xff0c;springboot项目创建过程详见 mac idea 创建 springboot 项目_JAVA&#xff24;WangJing的博客-CSDN博客_mac idea创建springboot项目 二、SpringBoot 整合使用 Rdis SpringBoot 项目 添加 redis配置_JAVA&#xff24;WangJing的博客…

Linux内存映射函数mmap与匿名内存块

学习系列&#xff1a;《APUE14.8》《CSAPP9.8.4》 1 总结 memory-mapped io可以将文件映射到内存中的buffer&#xff0c;当我们从buffer读写数据时&#xff0c;其实操作的是对应文件中的数据。这样可以达到不使用READ/WRITE的IO操作。mmap也可以直接映射匿名内存块&#xff0c…

零信任对企业安全防护能起到什么作用?

随着网络攻击的不断演变&#xff0c;变得更加普遍和复杂&#xff0c;企业正在寻找一种基于身份的网络安全新方法。这些策略和解决方案旨在保护企业内的所有人和机器&#xff0c;并用于检测和防止身份驱动的违规行为。 企业很容易被感染&#xff0c;但问题在于如何找到解决方案。…

基于花朵授粉算法的无线传感器网络部署优化附Matlab代码

✅作者简介&#xff1a;热爱科研的Matlab仿真开发者&#xff0c;修心和技术同步精进&#xff0c;matlab项目合作可私信。 &#x1f34e;个人主页&#xff1a;Matlab科研工作室 &#x1f34a;个人信条&#xff1a;格物致知。 更多Matlab仿真内容点击&#x1f447; 智能优化算法 …

hadoop集群安装(二):克隆服务器集群并免密

文章目录说明分享集群构建集群规划角色划分配置主机名、ip和主机名映射ssh免密总结说明 已 上篇 创建模型虚拟机为基础构建hadoop集群 分享 大数据博客列表开发记录汇总个人java工具库 项目https://gitee.com/wangzonghui/object-tool 包含json、string、集合、excel、zip压缩…

美食杰项目 -- 个人主页(四)

目录前言&#xff1a;具体实现思路&#xff1a;步骤&#xff1a;1. 展示美食杰菜谱大全效果2. 引入element-ui3. 代码总结&#xff1a;前言&#xff1a; 本文给大家讲解&#xff0c;美食杰项目中 实现个人主页的效果&#xff0c;和具体代码。 具体实现思路&#xff1a; 判断是…

如何复用ijkplayer库实现ffmpeg的功能

ijkplayer库介绍 现在ijkplayer播放器应用的非常广泛&#xff0c;很多播放器基本上都是基于ijkplayer二次迭代开发的&#xff0c;众所周知&#xff0c;ijkplayer是基于ffplay的&#xff0c;所以要使用ijkplayer&#xff0c;就必须使用三个so库。 jeffmonyJeffMonydeMacBook-P…

MySQL Server 和 MySQL Workbench安装

对于开发人员来说&#xff0c;只需要安装 MySQL Server 和 MySQL Workbench 这两个软件&#xff0c;就能满足开发的需要了。 ⚫ MySQL Server&#xff1a;专门用来提供数据存储和服务的软件。 ⚫ MySQL Workbench&#xff1a;可视化的 MySQL 管理工具&#xff0c;通过它&#…

会议信息管理系统SSM记录(四)

目录&#xff1a; &#xff08;1&#xff09;部门管理&#xff1a;查询-添加-删除 &#xff08;2&#xff09;部门管理&#xff1a;修改 &#xff08;1&#xff09;部门管理&#xff1a;查询-添加-删除 在DepartmentController中添加方法&#xff1a; 这里getAllDeps方法上篇…

Charles模拟弱网

Charles模拟弱网&#xff0c;适用PC端和移动端&#xff08;IOS&#xff0f;Android&#xff09; 1.打开Proxy->Throttle Settings&#xff0c;以charles 4.2版本为例 2.出现Throttling的界面 3.预设那里有Charles常用的网络设置模拟的数据&#xff0c;根据需要自己选择即可…

【js学习】闭包理解

闭包原理 js使用的是词法作用域&#xff0c;词法作用域的意味着函数执行使用的是定义函数时生效的变量作用域。即函数执行时&#xff0c;里面的参数访问跟执行函数时的作用域无关。如下代码2所示最后输出的是local scope。 闭包定义&#xff1a;js函数对象的内部状态不仅要包…

神经网络的基本工作原理——机器学习

目录 ​编辑 一、实验内容 二、实验过程 1、算法思想 2、算法原理 3、算法分析 三、源程序代码 四、运行结果及分析 五、实验总结 一、实验内容 掌握神经元细胞的数学模型&#xff1b;理解并掌握神经网络的训练过程&#xff1b;理解并掌握神经网络中的矩阵运算&#xff…

Neo4j入门实战

1.介绍 2.实战 Neo4j的sql语句 1.创建多个节点 CREATE (:student {name:小张,age:20}),(:student {name:彭莎丽,age:18})2.匹配节点将节点删除 这样删除是将student中的条件节点删除——>会变成空白节点 match (m:student {name:"Fairy同学"}) remove m:stu…

Docker数据管理

目录 一、数据卷 二、数据卷容器 三、容器互联 管理 Docker容器中数据主要有两种方式&#xff1a; 数据卷&#xff08;Data Volumes&#xff09;数据卷容器&#xff08;DataVolumes Containers&#xff09; 一、数据卷 数据卷是一个供容器使用的特殊目录&#xff0c;位于容…

设计模式之美——基于接口编程

抽象类在被继承时体现的是 is-a 关系&#xff0c;接口在被实现时体现的是 can-do 关系 例如&#xff0c;Plane can fly. Bird can fly&#xff0c;应该把 fly 定义成一个接口。 – 参考 《码出自效Java 开发手册》 函数的命名不能暴露任何实现细节。比如&#xff0c; uploadT…

集合类ArrayList的扩容机制详解

ArrayList类的内部对构造方法进行了重载&#xff0c;提供了无参构造和有参构造两种构造方法。在ArrayList类的内部维护了一个elementData数组用来存放添加的对象。(关于transient关键字&#xff1a;该关键字修饰的属性&#xff0c;不会进行串行化、序列化)。 目录 无参构造扩容…

胡编乱造的自我介绍

写在前面&#xff1a;这篇文章的内容纯属胡编乱造 切勿信以为真。 一、 姓&#xff1a;&#xff08;Black) 布莱克 名&#xff1a;&#xff08;Sirius) 天狼星 中间名&#xff1a;(Orion) 奥莱恩 结束&#xff01; 二、于 2022年3月 入读清华大学信息技术系硕士 &#xff01; 结…

harbor 安装

harbor 安装一.离线安装helm1.下载安装包&#xff0c;上传到服务器2.解压4.验证二.安装harbor1.导入 Harbor源2.下载 Harbor Helm目录3.解压缩4.修改 Harbor Values文件5.部署 Harbor6.访问 Harbor7.验证8.修改daemon.json9.登录Harbor、并push镜像验证10.登录Harbor页面验证是…