【数据结构初阶】顺序结构二叉树(堆)接口实现超详解

news2024/9/19 15:00:30

文章目录

  • 1.树
    • 1. 1 树的概念与结构
    • 1. 2 树的相关术语
    • 1. 3 树的表示
    • 1. 4 树形结构实际运用场景
  • 2.二叉树
    • 2. 1 概念与结构
    • 2. 2 特殊的二叉树
      • 2. 2. 1 满二叉树
      • 2. 2. 2 完全二叉树
    • 2. 3 二叉树存储结构
      • 2. 3. 1 顺序结构
      • 2. 3. 2 链式结构
  • 3. 实现顺序结构二叉树(小堆)
    • 3. 1 堆的概念与结构
    • 3. 2 堆的实现
      • 3. 2. 1 堆的定义
      • 3. 2. 2 初始化与销毁
      • 3. 2. 3 交换
      • 3. 2. 4 入堆与向上调整函数
      • 3. 2. 5 判空与取堆顶数据与堆的大小
      • 3. 2. 6 出堆与向下调整
      • 3. 2. 7 大堆


1.树

1. 1 树的概念与结构

树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。有一个特殊的结点,称为根结点根结点没有前驱结点
除根结点外,其余结点被分成 M(M>0)个互不相交的集合 T1、T2、…、Tm ,其中每一个集合Ti (1 <= i<= m)又是一棵结构与树类似的子树。每棵子树的根结点有且只有一个前驱,可以有0个或多个后继。因此,树是递归定义的。
树形结构中,子树之间不能有交集,否则就不是树形结构。其实就是一个节点不能有多个前驱
树可以看做是这样的:
树
是现实中的树倒过来的样子。

这里有一些不是树的结构存储作为对比
1

  1. 子树是不相交的(如果存在相交就是图了,图是另一种数据结构)
  2. 除了根结点外,每个结点有且仅有一个父结点
  3. 一棵N个结点的树有N-1条边

1. 2 树的相关术语

以此图为例:
1

  1. 父结点 / 双亲结点:若一个结点含有子结点,则这个结点称为其子结点的父结点; 如上图:A是B的父结点
  2. 子结点 / 孩子结点:一个结点含有的子树的根结点称为该结点的子结点; 如上图:B是A的孩子结点
  3. 结点的度:一个结点有几个孩子,他的度就是多少; 比如A的度为 6,F的度为 2,K的度为 0
  4. 树的度:一棵树中,最大的结点的度称为树的度; 如上图:树的度为 6
  5. 叶子结点 / 终端结点:度为 0 的结点称为叶结点;如上图:B、C、H、I…等结点为叶结点
  6. 分支结点 / 非终端结点:度不为 0 的结点; 如上图: D、E、F、G…等结点为分支结点
  7. 兄弟结点 : 具有相同父结点的结点互称为兄弟结点(亲兄弟); 如上图:B、C是兄弟结点
  8. 结点的层次 : 从根开始定义起,根为第 1 层,根的子结点为第 2 层,以此类推。
  9. 树的高度或深度:树中结点的最大层次; 如上图:树的高度为 4
  10. 结点的祖先 : 从根到该结点所经分支上的所有结点; 如上图:A是所有结点的祖先
  11. 路径 : 一条从树中任意节点出发,沿父节点 - 子节点连接,达到任意节点的序列; 比如A到Q的路径为:A - E - J - Q; H到O的路径H - D - A - E - J - Q
  12. 子孙 : 以某结点为根的子树中任一结点都称为该结点的子孙。如上图:所有结点都是A的子孙
  13. 森林 : 由m(m > 0)棵互不相交的树的集合称为森林

1. 3 树的表示

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

struct TreeNode
 {
	 struct Node* child; 	// 最左边的孩子结点
	 struct Node* brother; 	// 指向其右边的下一个兄弟结点
	 int data; 				// 结点中的数据域
};

1
以这个二叉树为例,假如想从A找到I,就是:

A->child->child->brother->brother->child->brother

通过这样的存储方式,我们可以找到树上的每一个节点。

1. 4 树形结构实际运用场景

文件系统是计算机存储和管理文件的一种方式,它利用树形结构来组织和管理文件和文件夹。在文件系统中,树结构被广泛应用,它通过父结点和子结点之间的关系来表示不同层级的文件和文件夹之间的关联。
1

2.二叉树

2. 1 概念与结构

在树形结构中,我们最常用的就是二叉树,一棵二叉树是结点的一个有限集合,该集合由一个根结点加上两棵别称为左子树和右子树的二又树组成或者为空
2
从上图可以看出二叉树具备以下特点:

  1. 二叉树不存在度大于 2 的结点
  2. 二叉树的子树有左右之分,次序不能颠倒,因此二又树是有序树

注意:对于任意的二又树都是由以下几种情况复合而成的
3

2. 2 特殊的二叉树

2. 2. 1 满二叉树

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

2

2. 2. 2 完全二叉树

完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树引出来的。
对于深度为K,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时(简单地讲,就是第X层没有完全放满时,X+1层不能有节点,且没有满的层的节点必须是从左往右排布的)称之为完全二叉树。
要注意的是满二叉树是一种特殊的完全二叉树

2
二叉树性质
根据满二叉树的特点可知:

  1. 若规定根结点的层数为 1,则一棵非空二叉树的第i层上最多有 2i-1 个结点
  2. 若规定根结点的层数为 1,则深度为 h 的二叉树的最大结点数是 2h-1
  3. 若规定根结点的层数为 1,具有 n 个结点的满二叉树的深度h = log2(n+1)(log以2为底,n+1为对数)

2. 3 二叉树存储结构

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

2. 3. 1 顺序结构

顺序结构存储就是使用数组来存储,一般使用数组只适合表示完全二叉树,因为不是完全二叉树会有空间的浪费,完全二又树更适合使用顺序结构存储
1
通常把(一种二叉树)使用顺序结构的数组来存储,需要注意的是这里的堆和操作系统虚拟进程地址空间中的堆是两回事,一个是数据结构,一个是操作系统中管理内存的一块区域分段。

2. 3. 2 链式结构

二叉树的链式存储结构是指,用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。
通常的方法是链表中每个结点由三个域组成,数据域和左右指针域左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址。链式结构又分为二叉链三叉链,现在我们接触的一般是二叉链。
高阶数据结构如红黑树等会用到三叉链。
2
3

3. 实现顺序结构二叉树(小堆)

一般堆使用顺序结构的数组来存储数据,堆是一种特殊的二叉树,具有二叉树的特性的同时,还具备其他的特性。

3. 1 堆的概念与结构

如果有一个关键码的集合K = {k0,k1,k2,…,kn-1},把它的所有元素按完全二叉树的顺序存储方式存储,在一个一维数组中,并满足:

Ki <= K2*i+1 (或Ki >= K2*i+1 且 Ki <= K2*i+2)

则称为小堆(或大堆)。将根结点最大的堆叫做最大堆或大根堆,根结点最小的堆叫做最小堆或小根堆
4
堆具有以下性质:

  1. 堆中某个结点的值总是不大于或不小于其父结点的值
  2. 堆总是一棵完全二叉树

二叉树性质:
对于具有 n 个结点的完全二叉树,如果按照从上至下从左至右的数组顺序对所有结点从0 开始编号,则对于序号为 i 的结点有:

  1. i>0i 位置结点的双亲序号为(i-1)/2;若i=0i为根结点编号,无双亲结点
  2. 2i+1<n,左孩子序号:2i+12i+1>=n 否则无左孩子
  3. 2i+2<n,右孩子序号:2i+22i+2>=n 否则无右孩子

3. 2 堆的实现

我们先以小堆为例进行实现:

3. 2. 1 堆的定义

//小堆
typedef int HPDataType;
typedef struct Heap
{
	HPDataType* arr;
	int size;
	int capacity;
}Heap;

其实可以看到,堆的底层结构和动态顺序表是一模一样的,所以部分接口和顺序表也是一样的,就简略介绍了。

3. 2. 2 初始化与销毁

创建
将所有成员变量置为NULL0
main函数中创建堆变量再传入进行初始化,可以避免在销毁时对堆变量本身进行free

void HeapInit(Heap* hp)
{
	assert(hp);
	hp->arr = NULL;
	hp->capacity = hp->size = 0;
}

销毁
把动态申请的数组free掉,再把所有成员置为NULL0就可以了。

void HeapDestory(Heap* hp)
{
	assert(hp);
	free(hp->arr);
	hp->arr = NULL;
	hp->capacity = hp->size = 0;
}

3. 2. 3 交换

交换两个数的位置,下面的函数需要用到。

void Swap(HPDataType* a, HPDataType* b)
{
	HPDataType tmp = *a;
	*a = *b;
	*b = tmp;
}

3. 2. 4 入堆与向上调整函数

这里就是堆和顺序表不同的部分了。

如果我们想向堆中加入一个数据,该怎么做?
直接将它放进数组就完成了吗?
当然不行,为了确保在插入一个数据之后这个数组还能表示一个堆,我们需要先实现一个向上调整算法,在向顺序表插入新数据后,将这个数据向上调整到合适的位置来确保堆的正确

//向上调整
void AdjustUp(HPDataType* arr, int child);

参数arr是堆的底层数组,参数child是要向上调整的数据。

要怎么向上调整?
我们可以找到child的父节点parent,如果parent对应的数据比child小,就说明现在已经不用再进行调整了,堆已经正确了。如果如果parent对应的数据比child大,就交换这两个数。如果调整一次之后堆仍然不正确,就继续上面的步骤直到堆正确
如图:
2

void AdjustUp(HPDataType* arr, int child)
{
	assert(arr);
	int parent = (child - 1)/2;	//父节点
	while (child)	//注意child可能会被调整到根节点,这时候就不能再调整了
	{
		if (arr[child] < arr[parent])	//如果条件语句不成立,就说明堆已经成型
		{
			Swap(&arr[child], &arr[parent]);
			child = parent;
			parent = (child - 1) / 2;	//循环以上步骤
		}
		else
			break;
	}
}

接下来看入堆
有以下两个步骤:

  1. 参考顺序表向数组尾插数据
  2. 向上调整
void HeapPush(Heap* hp, HPDataType x)
{
	assert(hp);
	//插入数据
	if (hp->size == hp->capacity)
	{
		//扩容
		int newcapacity = hp->capacity == 0 ? 2 : 2 * hp->capacity;
		HPDataType* new = (HPDataType*)realloc(hp->arr, newcapacity * sizeof(HPDataType));
		if (!new)
		{
			perror("realloc");
			exit(1);
		}
		hp->arr = new;
		hp->capacity = newcapacity;
	}
	hp->arr[hp->size++] = x;
	//向上调整
	AdjustUp(hp->arr, hp->size - 1);
}

3. 2. 5 判空与取堆顶数据与堆的大小

判空

bool HeapEmpty(Heap* hp)
{
	assert(hp);
	return hp->size == 0;
}

取堆顶数据
注意堆顶的下标是0。

HPDataType HeapTop(Heap* hp)
{
	assert(hp && hp->size);
	return hp->arr[0];
}

堆的大小

int HeapSize(Heap* hp)
{
	assert(hp);
	return hp->size;
}

3. 2. 6 出堆与向下调整

堆这个数据结构如果要删除数据只能从堆顶删除(可以类比现实中的堆,肯定不能从中间抽数据,不然堆就塌了)。那该怎么删除数据呢?
直接将堆顶元素删除吗?如果是这样,就要把后面所有元素向前挪动一位,而且两个孩子节点谁当父节点也是个问题。(在纸上画一画,很容易会发现这样删除真的很复杂)
所以更合适的办法应该是将堆顶元素与堆的最后一个元素交换位置,将最后一个元素删除,再进行向下调整算法

//向下调整算法
void AdjustDown(HPDataType* arr, int parent, int n);

2
向下调整和向上调整的思想是一样的,都是通过比较父节点与孩子节点并交换,直到堆是正确的。
注意这里父节点应该和较小的孩子节点进行比较与交换,来确保交换(如果发生交换)之后父节点大于孩子节点。

向下调整算法还有一个前提:左右子树必须是一个堆,才能调整。也就是说在调整之前,除了堆顶元素外,堆必须是正确的。
2

void AdjustDown(HPDataType* arr, int parent, int n)
{
	assert(arr);
	int child = 2 * parent + 1;//左孩子
	while (child < n)	//如果已经调整到了叶子节点,child就已经大于n了,不能再调整了
	{
		//找较小的孩子,child++就是右孩子了
		if (child + 1 < n && arr[child] > arr[child + 1])
			child++;
		//这里和向上调整就一样了,比较,交换,循环
		if (arr[child] < arr[parent])
		{
			Swap(&arr[child], &arr[parent]);
			parent = child;
			child = 2 * parent + 1;
		}
		else
			break;
	}
}

接下来就是出堆的完整函数了:

void HeapPop(Heap* hp)
{
	assert(hp && hp->size);
	//交换堆顶和最后一个元素
	Swap(&hp->arr[0], &hp->arr[hp->size - 1]);
	//删除最后一个元素(原来的堆顶)
	hp->size--;
	//向下调整
	AdjustDown(hp->arr, 0, hp->size);
}

3. 2. 7 大堆

实现小堆之后,只需要在两个调整算法中更改一下调整的判断条件(比如原来是父节点比子节点大就交换,更改成父节点比子节点小就交换)就能实现大堆了,这里就不再赘述了。

谢谢你的阅读,喜欢的话来个点赞收藏评论关注吧!
我会持续更新更多优质文章

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

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

相关文章

【OJ刷题】双指针问题6

这里是阿川的博客&#xff0c;祝您变得更强 ✨ 个人主页&#xff1a;在线OJ的阿川 &#x1f496;文章专栏&#xff1a;OJ刷题入门到进阶 &#x1f30f;代码仓库&#xff1a; 写在开头 现在您看到的是我的结论或想法&#xff0c;但在这背后凝结了大量的思考、经验和讨论 目录 1…

ThreadX源码:Cortex-A7的tx_thread_irq_nesting_start(嵌套中断开始动作).s汇编代码分析

0 参考资料 Cortex M3权威指南(中文).pdf&#xff08;可以参考ARM指令集用法&#xff09; 1 前言 tx_thread_irq_nesting_start.s是用来实现Cortex-A7 IRQ嵌套中断的开始函数实现的汇编文件。 2 源码分析 源码如下&#xff1a; &#xff11;  IRQ_DISABLE 0x80…

遍历指定的目录a中的所有子目录及所有文件os.walk(root_dir)

【小白从小学Python、C、Java】 【考研初试复试毕业设计】 【Python基础AI数据分析】 遍历指定的目录a 中的所有子目录 及所有文件 os.walk(root_dir) [太阳]选择题 已知已经存在的目录和文件情况如下&#xff1a; a目录下有子目录b&#xff0c;有两个文件&#xff1a;a1.txt和…

Flet全平台开发:软件开发界勇士为Python语言补短板的一次极具挑战性的尝试、冲刺和华丽亮相

一、Flet创始人和开发者介绍、开发Flet的背景介绍 Flet 的创始人和开发者 Feodor Fitsner 是俄罗斯人&#xff0c;就职于微软。 Flet 的第一个版本于 2022 年 6 月发布。这是一个相对较新的库&#xff0c;它基于 Flutter 框架&#xff0c;首先支持的是用 Python 语言开发软件…

2024/9/17 pytorch-卷积神经网络

一、torch.nn pytorch有很多接口&#xff0c;其中的torch.nn可以让我们方便的调用以便生成神经网络各层 1.torch.nn.Module 是一个构成神经网络层的一个基本类别&#xff0c;一般生成一个类别来继承nn.module torch.tensor(a)将a初始化为一个tensor类型数据 一般这种已经固…

攻防世界--->hackme

学习笔记。 下载 查壳。 64ida打开。 进入main&#xff1a; 跟进&#xff1a; 这是密文 咋一看这程序感觉很复杂&#xff0c;很复杂&#xff1a; 脚本&#xff1a; #include <stdio.h> #include <string.h> #include <stdlib.h>int main() {unsigned char …

Qt --- 信号和信号槽

前言 Linux信号Signal&#xff0c;系统内部的通知机制&#xff0c;进程间通信方式。 信号源&#xff1a;谁发的信号。 信号的类型&#xff1a;哪种类别的信号。 信号的处理方式&#xff1a;注册信号处理函数&#xff0c;在信号被触发的时候自动调用执行。 Qt中的信号和Lin…

Bugku---密码学---乐谱密码

题目出处&#xff1a;首页 - Bugku CTF ✨打开后发现是一张乐符图 ✨一般我们所熟悉的「Do Re Mi Fa Sol La Si」&#xff0c;若写成音名&#xff0c;即是「C D E F G A B」。不过德国人习惯使用的音名则是「C D E F G A H」&#xff0c;「B」代表 音名B♭ 。 C也就是后面的4&…

Rust练手项目,写个有趣的小工具定时从一言网获取一段有趣的话并推送通知

Rust练手项目&#xff0c;写个有趣的小工具 代码 继续练习Rust, 写个小工具定时从一言网获取一段有趣的话并提示&#xff0c;如下 练习以下Rust点 并发编程 Mutex, Arc指针使用HTTP请求Windows Gui 代码 Cargo.toml [package] name "funny_word" edition "20…

YOLOv8目标检测模型——遥感小目标检测经验分享

小目标检测——YOLOV8 一、引言 背景介绍 &#xff08;1&#xff09;目标检测的重要性 目标检测在许多领域都具有极其重要的作用。在自动驾驶中&#xff0c;目标检测能够识别道路上的障碍物和行人&#xff0c;确保行车安全。在视频监控中&#xff0c;目标检测能够实时发现异…

【matlab】生成 GIF 的函数(已封装可直接调用)

文章目录 前言一、函数输入与输出二、函数代码三、例程&#xff08;可直接运行&#xff09;参考文献 前言 生成 gif 图片时遇到的问题&#xff0c;为了后续调用方便&#xff0c;封装为函数 一、函数输入与输出 输入&#xff1a; cell_figure: cell 数组&#xff0c;数组元素是…

Chainlit集成LlamaIndex并使用通义千问模型实现AI知识库检索网页对话应用增强版

前言 之前使用Chainlit集成LlamaIndex并使用通义千问大语言模型的API接口&#xff0c;实现一个基于文档文档的网页对话应用。 可以点击我的上一篇文章《Chainlit集成LlamaIndex并使用通义千问模型实现AI知识库检索网页对话应用》 查看。 本次针对上一次的代码功能进一步的完善…

Cursor与Copilot:编程界的双雄对决

引子 在技术快速发展的当下&#xff0c;编程几乎成为了现代社会的基础能力。Cursor与Copilot作为当前备受瞩目的编程助手&#xff0c;各自展现出了独特的魅力。它们不仅改变了程序员的工作方式&#xff0c;更是提升了代码编写的效率&#xff0c;成为了编程界的“双雄”。 Curs…

软件安全、逆向分析、加密与解密--crackme2详解

本次使用到的软件有&#xff1a;PEiD、IDA、X32dbg 刚学逆向不久&#xff0c;可能有些地方会有错误&#xff0c;欢迎各位大佬指导 执行 运行程序 点击About 点击确定&#xff0c;输入如图数据 点击try Now 点击确定&#xff0c;回到主界面 点击Exit&#xff0c;退出 查壳&a…

Docker:SpringBoot项目创建Docker镜像并推送到阿里云容器镜像仓库

0. 准备工作 os&#xff1a;macos 15.0 jdk&#xff1a;1.8 docker&#xff1a;26.0.0 1. 阿里云容器镜像服务创建实例 创建个人版 个人实例创建成功 个人镜像加速器地址 2. 安装Docker Desktop Docker Desktop是Docker的一个集成工具&#xff0c;非必须&#xff0c;过程…

指纹与指甲检测系统源码分享

指纹与指甲检测检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Computer V…

这个时代唯一“不变“的又是{变}

这个时代唯一不变的就是“变”&#xff0c;所以每个人都得有规划意识&#xff0c;首先要对自己的价值有清晰的认知&#xff0c;你核心卖点是什么。第二&#xff0c;你取得的成绩是通过平台成就的还是通过自身努力取得的&#xff0c;很多人在一家平台待久了之后&#xff0c;身上…

在Unity UI中实现UILineRenderer组件绘制线条

背景介绍 在Unity的UI系统中&#xff0c;绘制线条并不像在3D世界中那样直观(使用Unity自带的LineRender组件在UI中连线并不方便,它在三维中更合适)。没有内置的工具来处理这种需求。如果你希望在UI元素之间绘制连接线&#xff08;例如在UI上连接不同的图标或控件&#xff09;&a…

26. 构建一个矩形平面几何体

定义矩形几何体顶点坐标 一个矩形平面&#xff0c;可以至少通过两个三角形拼接而成。而且两个三角形有两个顶点的坐标是重合的。 注意三角形的正反面问题&#xff1a;保证矩形平面两个三角形的正面是一样的&#xff0c;也就是从一个方向观察&#xff0c;两个三角形都是逆时针…

Docker 消息队列RabbitMQ 安装延迟消息插件

介绍 RabbitMQ的官方推出了一个插件&#xff0c;原生支持延迟消息功能。该插件的原理是设计了一种支持延迟消息功能的交换机。当消息投递到交换机后可以暂存一定时间&#xff0c;到期后再投递到队列。 查看版本号 docker exec rabbit名字 rabbitmqctl version根据版本下载 插…