【数据结构初阶 6】二叉树:堆的基本操作 + 堆排序的实现

news2024/11/14 18:10:53

文章目录

  • 🌈 Ⅰ 二叉树的顺序结构
  • 🌈 Ⅱ 堆的概念与性质
  • 🌈 Ⅲ 堆的基本操作
    • 01. 堆的定义
    • 02. 初始化堆
    • 03. 堆的销毁
    • 04. 堆的插入
    • 05. 向上调整堆
    • 06. 堆的创建
    • 07. 获取堆顶数据
    • 08. 堆的删除
    • 09. 向下调整堆
    • 10. 判断堆空
  • 🌈 Ⅳ 堆的基本应用
    • 01. 堆排序的实现
    • 02. TOP K 问题

🌈 Ⅰ 二叉树的顺序结构

1. 顺序存储结构概念

  • 顺序存储结构就是使用数组来存储二叉树的数据。
  • 这种结构下的逻辑结构是二叉树物理结构是数组
  • 数组内的值是将二叉树自上而下、自左而右依次存储,反过来数组构建二叉树也是这个顺序。

在这里插入图片描述

2. 顺序存储结构优势

使用这种结构可以很容易得出父子结点的下标

  • 双亲结点下标 = ( 左或右孩子结点下标 - 1 ) / 2
  • 左孩子结点下标 = 双亲结点下标 * 2 + 1
  • 右孩子结点下标 = 双亲结点下标 * 2 + 2

3. 适合顺序存储的二叉树

  • 只有满二叉树完全二叉树这种能够有效利用数组空间,适合使用顺序存储。

🌈 Ⅱ 堆的概念与性质

1. 堆的概念

  • 将一组数据构建成一棵完全二叉树,如果根节点的值 大于 / 小于 左右子树的所有值,则称该完全二叉树为一个堆。
  • 将根节点最大的堆称做大根堆;将根节点最小的堆称为小根堆。

2. 堆的性质

  1. 堆总是一棵完全二叉树。
  2. 有序数组一定是堆,反之却不一定。
  3. 小根堆:堆中所有双亲结点的值总是 <= 其孩子结点,根结点的值最小。
  4. 大根堆:堆中所有双亲结点的值总是 >= 其孩子结点,根结点的值最大。

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

🌈 Ⅲ 堆的基本操作

01. 堆的定义

  • 堆在计算机看来实际就是个数组,但不能只用数组表示堆,还需要记录下每个堆的有效数据个数以及对应堆的容量。
  • 因此就要建立一个堆的结构体来管理每个堆。
typedef int HPDataType;	// 堆中每个结点的数据类型

typedef struct Heap		
{
	int size;			// 记录数组中有效数据个数
	int capacity;		// 记录开辟的数组空间大小
	HPDataType* data;	// 为堆空间开辟的数组
}Heap;
  • 注意:因为 size 是用来记录堆中有效数据的个数,因此 size 天生是最后一个有效数据的后一个位置的下标。

02. 初始化堆

void HeapInit(Heap* hp)
{
	assert(hp);

	hp->data = NULL;
	hp->size = hp->capacity = 0;
}

03. 堆的销毁

void HeapDestory(Heap* hp)
{
	assert(hp);

	free(hp->data);
	hp->data = NULL;
	hp->size = hp->capacity = 0;
}

04. 堆的插入

  • 堆的本质实际上是个数组,因此往堆中插入数据就将数据尾插到数组中。
  • 当前有一组数据为 [68, 34, 49, 25, 18, 19, 15] 的数组构成的大根堆,往最后插入一个 10。

在这里插入图片描述

void HeapPush(Heap* hp, HPDataType x)
{
	assert(hp);

	if (hp->capacity == hp->size)		//是否要扩容
	{
		int newcapacity = hp->capacity = 0 ? 4 : 2 * hp->capacity;
		
		HPDataType* tmp = 
			(HPDataType*)realloc(hp->data, newcapacity * sizeof(HPDataType));
		assert(tmp);
		
		hp->capacity = newcapacity;
		hp->data = tmp;
	}

	hp->data[hp->size++] = x;			//插入新数据
	AdjustUp(hp->data, hp->size - 1);	//堆向上调整
}

05. 向上调整堆

1. 为何要向上调整堆

  • 插入数据之后可能导致破坏堆的结构,可能要对堆进行调整。
  • 往一个堆中插入不同的值时,需要判断会不会破会堆的结构。
    • 下图中,插入了 10 不会破坏大根堆,插入 100 却会。

在这里插入图片描述

2. 根据堆的性质判断是否要调堆

  • 小根堆中:只需要判断新插入的数据是否 < 其双亲结点的值,如果是则和其双亲结点交换。

  • 大根堆中:只需要判断新插入的数据是否 > 其双亲结点的值,如果是则和其双亲结点交换。

  • 在交换了之后,新结点可能比它双亲的双亲还 小 / 大,要一直交换到符合堆的定义为止。

    • 新结点 100 和它的双亲交换之后还是大于其新的双亲,要交换到符合堆定义为止。
  • 如下图所示的将 100 向上调整到它最终位置,即为堆的向上调整。

在这里插入图片描述

3. 堆的向上调整实现思路

  • 定义的函数形参 data 是一个存储堆中数据的数组,child 是新插入的结点的下标。
  • 算出新结点的双亲结点,然后与其双亲结点比较,如果不符合 大 / 小根堆的定义则交换。
  • 交换了之后原来双亲结点的位置就变为了新结点的位置,再算出该结点新的双亲结点去比较。
  • 当将新结点向上调整到符合 大 / 小 根堆的定义时停止调整,最坏情况新结点会被调成根结点。

4. 堆的向上调整代码实现

  • 该代码适用于调成 大 / 小 根堆。
void AdjustUp(HPDataType* data, int child)		// 向上调整堆
{
	int parent = (child - 1) / 2;				// 算出新结点的双亲结点

	while (child > 0)							// 最坏情况新结点会被调成根结点
	{
		// if (data[child] < data[parent])		// 按照 小根堆 定义向上调整
		if (data[child] > data[parent])			// 按照 大根堆 定义向上调整
		{
			swap(&data[child], &data[parent]);	// 交换双亲和孩子结点的数据
			child = parent;						// 原双亲结点的位置给了新结点
			parent = (parent - 1) / 2;			// 求新结点双亲的双亲的位置
		}
		else									// 结点被调到符合 大/小 根堆
		{
			break;								
		}
	}
}

06. 堆的创建

实现思路

  • 将一组数据从第一个开始依次进堆,每放一个数据进堆就调用一次向上调整算法。
    • 当前有一组数据,将它们依次插入进堆,然后调用向上调整算法。

在这里插入图片描述

代码实现

int main()
{
	int test[] = {85,9,1,7,6,7,5,45,13,54};
	size_t size = sizeof(test) / sizeof(test[0]);

	Heap hp;
	HeapInit(&hp);

	// 将 test 数组内的值依次插入进堆
	for (int i = 0; i < size; i++)
	{
		HeapPush(&hp, test[i]);
	}

	return 0;
}

07. 获取堆顶数据

  • 数组的 0 号位置就是堆顶元素,直接返回该位置的值即可。
HPDataType HeapTop(Heap* hp)
{
	assert(hp);
	assert(hp->size > 0);	// 堆中有元素可被获取

	return hp->data[0];		// 堆中结点的值不一定是 int 类型
}

08. 堆的删除

  • 堆的删除规定删除根结点的数据,即删除堆顶结点。

实现思路

  • 将堆顶元素和堆尾元素交换,然后将堆中有效数据个数 -1 即可实现删除。

在这里插入图片描述

代码实现

void HeapPop(Heap* hp)
{
	assert(hp);
	assert(hp->size > 0);							// 堆中有元素

	swap(&hp->data[0], &hp->data[hp->size - 1]);	// 堆顶和堆尾互换
	hp->size--;										// 删除最后一个元素
	AdjustDown(hp->data, hp->size, 0);				// 将堆顶元素向下调整
}

09. 向下调整堆

1. 为何要向下调整堆

  • 某些情况下,堆中的某一个非叶子结点可能要比其孩子结点 大 / 小,不符合 小 / 大 根堆的定义。
    • 如上图:将 9 换到根结点之后明显就破坏了大根堆的结构,要将其向下调整到合适位置。

2. 向下调整实现思路

  1. 比较要下沉的结点 k 的左右孩子的值,找出值较 大 / 小 的那个孩子出来。
  2. 如果是大根堆,就用最大孩子和 k 互换;如果是小根堆,就用最小孩子和 k 互换。
  3. 重复上述步骤,直到将 k 调到它应在的位置即可。

在这里插入图片描述

3. 向下调整代码实现

  • 按照小根堆的定义向下调整
void AdjustDown(HPDataType* data, int size, int parent)
{
	int child = parent * 2 + 1;	// 假设是结点的左孩子比较小

	while (child < size)		// 不能超过数组的范围
	{
		// 如果右孩子 < 左孩子,则最小孩子结点换成右孩子
		if (child + 1 < size && data[child + 1] < data[child])
		{
			child++;
		}

		//最小孩子结点 < 其双亲结点则要交换
		if (data[child] < data[parent])
		{
			swap(&data[child], &data[parent]);
			child = child * 2 + 1;
			parent = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}
  • 按照大根堆的定义向下调整,将两个 if 里用于比较左右孩子大小的 < 换成 > 即可。
    • 第一个 if:将 data[child + 1] < data[child] 换成 data[child + 1] > data[child]
    • 第二个 if:将 data[child] < data[parent] 换成 data[child] > data[parent]
void AdjustDown(HPDataType* data, int size, int parent)
{
	int child = parent * 2 + 1;	// 假设是结点的左孩子比较大

	while (child < size)		// 不能超过数组的范围
	{
		// 如果右孩子 > 左孩子,则最大孩子结点换成右孩子
		if (child + 1 < size && data[child + 1] > data[child])
		{
			child++;
		}

		//最大孩子结点 > 其双亲结点则要交换
		if (data[child] > data[parent])
		{
			swap(&data[child], &data[parent]);
			child = child * 2 + 1;
			parent = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

10. 判断堆空

  • 判断堆中有效数据的个数是否为 0 即可。
int HeapEmpty(Heap* hp)
{
	assert(hp);

	return 0 == hp->size;
}

🌈 Ⅳ 堆的基本应用

01. 堆排序的实现

排序思路

  • 事先声明:排升序用大根堆,排降序用小根堆 (默认为升序)
  1. 将待排序的 n 个数据使用向下调整造成一个大根堆,此时堆顶就是整个数组的最大值。
  2. 将堆顶和堆尾互换,此时堆尾的数就变成了最大值,剩余的待排序数组元素个数为 n - 1 个。
  3. 将剩余的 n - 1 个数调整回大根堆,将新的大根堆的新的堆顶和新的堆尾互换。
  4. 重复执行上述步骤,即可得到有序数组。

举个例子

  • 当前有数据为 [ 8, 9, 4, 74, 12, 15, 6 ] 现对其进行升序排序,要先构成大根堆。

在这里插入图片描述

代码实现

  • data 指向原数组空间,n 表示要排序的数据个数。
// 排成升序
void HeapSort(int* data, int n)
{
	int i = 0;
	int end = n - 1;

	// 从最后一个非叶子结点开始依次往前向下调整构建大根堆
	// n - 1 是最后一个结点的下标,(n - 1 - 1) / 2 是最后一个结点的夫结点下标
	// 也就是最后一个非叶子结点
	for (i = (n - 1 - 1) / 2; i >= 0; i--)
	{
		// 要使用建大堆的向下调整算法
		AdjustDown(data, n, i);
	}

	// 0 和 end 夹着的是待排序数据,end 是待排序数据的个数
	// 每次都选出一个最大的数放到 end 处,然后待排序数据个数 end - 1
	while (end > 0)
	{
		swap(&data[0], &data[end]);	// 互换堆顶和堆尾的数据
		AdjustDown(data, end, 0);	// 从根位置 (0) 开始的向下调整
		end--;						// 缩小待排序数据区间,且个数 - 1
	}
}

02. TOP K 问题

问题概述

  • 在 n 个数中找出最大 / 最小的前 k 个数 (前提:n 远大于 k)

实现思路

  1. 用这 n 个数的前 k 个数来构建一个堆,这个堆就只有 k 个数。
    • 求前 k 个最大的元素,就建小根堆。
    • 求前 k 个最小的元素,就建大根堆。
  2. 用剩余的 n - k 个元素依次与堆顶元素比较。
    • 求前 k 个最大的元素,就用比小根堆顶 大 的数和其互换,然后向下调整堆。
    • 求前 k 个最小的元素,就用比大根堆顶 小 的数和其互换,然后向下调整堆。

举个栗子

  • 当前有如下一组数据,现求其最大的前 3 个数
    • [ 4, 6, 5, 2, 3, 7, 9, 1, 8 ]
  • 建成小堆能将后面比堆顶小的数全部挡在外面,最后堆中剩下的 3 个值就是最大的那三个。

在这里插入图片描述

代码实现

void TopK(int* data, int n, int k)
{
	int i = 0;
	int j = 0;
	HPDataType* MinHeap = (HPDataType*)malloc(sizeof(HPDataType) * k);
	assert(MinHeap);

	for (i = 0; i < k; i++)				// 将前 k 个数先插入进堆中
	{
		MinHeap[i] = data[i];
	}
	
	for (i = (k - 2) / 2; i >= 0; i--)	// 将这 k 个数的堆向下调整成小根堆
	{
		AdjustDown(MinHeap, k, i);
	}
	
	for(j = k; j < n; j++)				// 将 k 之后的数据依次和堆顶比较
	{
		if (MinHeap[0] < data[j])		// 后续数据大于堆顶则和堆顶互换后调整
		{
			MinHeap[0] = data[j];
			AdjustDown(MinHeap, k, 0);
		}
	}
}

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

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

相关文章

【Git企业实战开发】Git常用开发流操作总结

【Git企业实战开发】Git常用开发流操作总结 大家好 我是寸铁&#x1f44a; 总结了一篇Git常用开发流操作总结的文章✨ 喜欢的小伙伴可以点点关注 &#x1f49d; 现在刚做项目的伙伴&#xff0c;可能你之前学过git&#xff0c;但是一实战发现不熟悉 没关系&#xff0c;看寸铁这篇…

色彩搭配:打造视觉吸引力与用户体验的关键

title: 色彩搭配&#xff1a;打造视觉吸引力与用户体验的关键 date: 2024/2/22 12:01:11 updated: 2024/2/22 12:01:11 tags: 网站色彩搭配视觉吸引力品牌形象用户体验设计色彩心理学配色技巧色轮互补 在当今数字化时代&#xff0c;网站已经成为了人们获取信息、进行交流和进行…

fatal error: costmap_2d/keepOutZone.h

fatal error: costmap_2d/keepOutZone.h: No such file or directory 7 | #include "costmap_2d/keepOutZone.h" 解决&#xff1a; #include "costmap_plugins/keepOutZone.h"代码中搜索 costmap_2d&#xff0c;全部替换成costmap_plugins&#xff1b…

【Unity】Unity与安卓交互

问题描述 Unity和安卓手机进行交互&#xff0c;是我们开发游戏中最常见的场景。本教程将从一个简单的例子来演示一下。 本教程需要用到Android Studio2021.1.1 1.Android Studio新建一个工程 2.选择Empty Activity 然后点击Next 3.点击Finish完成创建 4.选择File-New-New Mo…

Less预处理器教程

学习源码可以看我的个人前端学习笔记 (github.com):qdxzw/frontlearningNotes 觉得有帮助的同学&#xff0c;可以点心心支持一下哈 一、Less介绍 less官方文档 lesscss.org/ less中文文档 less.bootcss.com/ less是一种css预处理器&#xff0c;它扩展了css语言&#xff0c…

用Python插入页码到PDF文档

页码是许多类型文件中的重要内容&#xff0c;它能方便读者在文档中的导航。在创建PDF文档时&#xff0c;添加页码对于组织和引用内容特别有用。在本文中&#xff0c;我们将探讨如何利用Python程序高效地插入页码到PDF文档中&#xff0c;简化工作流程并创建出精美、结构合理的PD…

Eureka注册中心(黑马学习笔记)

Eureka注册中心 假如我们的服务提供者user-service部署了多个实例&#xff0c;如图&#xff1a; 大家思考几个问题&#xff1a; order-service在发起远程调用的时候&#xff0c;该如何得知user-service实例的ip地址和端口&#xff1f; 有多个user-service实例地址&#xff0c…

从 Elasticsearch 到 Apache Doris,统一日志检索与报表分析,360 企业安全浏览器的数据架构升级实践

导读&#xff1a;随着 360 企业安全浏览器用户规模的不断扩张&#xff0c;浏览器短时间内会产生大量的日志数据。为了提供更好的日志数据服务&#xff0c;360 企业安全浏览器设计了统一运维管理平台&#xff0c;并引入 Apache Doris 替代了 Elasticsearch&#xff0c;实现日志检…

Sulfo Cyanine3 dCTP,磺化-Cy3-dCTP,可以实时监测DNA的合成过程

Sulfo-Cy3-dCTP&#xff0c;Sulfo Cyanine3 dCTP&#xff0c;磺化-Cy3-dCTP&#xff0c;可以实时监测DNA的合成过程 您好&#xff0c;欢迎来到新研之家 文章关键词&#xff1a;Sulfo-Cy3-dCTP&#xff0c;Sulfo Cyanine3 dCTP&#xff0c;磺化-Cy3-dCTP 一、基本信息 产品简…

隐藏饿了么el-select组件的el-select-dropdown部分,只使用el-select的显示框

隐藏饿了么el-select组件的el-select-dropdown部分,只使用el-select的显示框 问题: 由于el-select组件的el-select-dropdown部分是自动插入在最外层Body上的&#xff0c;所以在当前组件的scoped中让el-select-dropdown组件display:none不会生效所以需要&#xff1a; :popper-…

企业数字化转型必备:大数据如何助力企业腾飞?

随着数字技术的迅猛发展&#xff0c;企业数字化转型已经成为一种不可逆转的趋势。在这个过程中&#xff0c;大数据扮演了至关重要的角色。那么&#xff0c;大数据究竟如何助力企业腾飞呢&#xff1f;接下来&#xff0c;我们将从多个角度深入剖析。 一、大数据助力企业精准决策 …

【MySQL】数据库索引详解 | 聚簇索引 | 最左匹配原则 | 索引的优缺点

创作不易&#xff0c;本篇文章如果帮助到了你&#xff0c;还请点赞 关注支持一下♡>&#x16966;<)!! 主页专栏有更多知识&#xff0c;如有疑问欢迎大家指正讨论&#xff0c;共同进步&#xff01; &#x1f525;c系列专栏&#xff1a;C/C零基础到精通 &#x1f525; 给大…

糖尿病性视网膜病变(DR)的自动化检测和分期

糖尿病性视网膜病变&#xff08;DR&#xff09;的自动化检测和分期 提出背景DR的阶段及其特征 历年解法计算机视觉方法多分类方法 新的解法深度学习方法迁移学习大模型多模型集成全流程分析 总结特征1&#xff1a;图像分割特征2&#xff1a;疾病分级特征3&#xff1a;治疗建议生…

邻接矩阵乘法 最短距离

问题一 最短距离 先从一个问题说起&#xff0c;输入 [4, 1, 4, 6, 4, 0, 6]代表依赖关系&#xff0c;从1开始&#xff0c;0代表ROOT节点。第二个元素为1代表第二个term在依赖树上的父节点为第1个term。​ 输出​ [[0 1 0 0 0 0 0]​ [0 0 0 0 0 0 0]​ [0 0 0 0 0 0 0]​ [1 …

【Java从入门到精通】Java Character 类

Java Character 类 Character 类用于对单个字符进行操作。 Character 类在对象中包装一个基本类型 char 的值 实例 char ch a;// Unicode 字符表示形式 char uniChar \u039A; // 字符数组 char[] charArray { a, b, c, d, e }; 然而&#xff0c;在实际开发过程中&#xf…

STM32—触摸键

目录 1 、 电路构成及原理图 2 、编写实现代码 3、代码讲解 4、烧录到开发板调试、验证代码 5、检验效果 此笔记基于朗峰 STM32F103 系列全集成开发板的记录。 1 、 电路构成及原理图 触摸键简单的了解就是一次电容的充放电过程。从原理图可以看出&#xff0c;触摸键 …

ORACLE数据库OCP认证

OCP证书是什么&#xff1f; OCP&#xff0c;全称Oracle Certified Professional&#xff0c;是Oracle公司的Oracle数据库DBA&#xff08;Database Administrator&#xff0c;数据库管理员)认证课程。这是Oracle公司针对数据库管理领域设立的一项认证课程&#xff0c;旨在评估和…

分类预测 | Matlab实现CWT-DSCNN-MSA基于时序特征、cwt小波时频图的双流卷积融合注意力机制的分类预测

分类预测 | Matlab实现CWT-DSCNN-MSA基于时序特征、cwt小波时频图的双流卷积融合注意力机制的分类预测 目录 分类预测 | Matlab实现CWT-DSCNN-MSA基于时序特征、cwt小波时频图的双流卷积融合注意力机制的分类预测分类效果基本描述程序设计参考资料 分类效果 基本描述 1.Matlab…

查询数据库的编码集Oracle,MySQL

1、查询数据库的编码集Oracle,MySQL 1.1、oracle select * from v$nls_parameters where parameterNLS_CHARACTERSET; 查询版本&#xff1a;SELECT * FROM v$version 2、MySQL编码集 SELECT DEFAULT_CHARACTER_SET_NAME, DEFAULT_COLLATION_NAME FROM information_schema.SC…

汇编语言movs指令学习

字符串传送指令(Move String Instruction) movs 该指令是把指针DS:SI所指向的字节、字或双字传送给指针ES:DI所指向内存单元&#xff0c;并根据标志位DF对寄存器DI和SI作相应增减。该指令的执行不影响任何标志位。 记不清这指令是8086就有的&#xff0c;还是386以后新加的&…