数据结构-堆-详解

news2024/9/22 15:27:29

数据结构-堆-详解

  • 1.性质
      • 大根堆
      • 小根堆
  • 2.实现
    • 2.1struct Heap、HeapInit、HeapDestroy
    • 2.2HeapPush
      • AdjustUp
    • 2.3HeapPop
      • AdjustDown
    • 2.4HeapTop、HeapSize、HeapEmpty
  • 3.应用
    • 3.1堆排
      • 建堆
      • 排序
    • 3.2TopK问题

1.性质

堆是一种特殊的完全二叉树,其父节点总是不大于/不小于 子节点。
相比于普通二叉树,堆更适合用顺序结构的数组存储。

大根堆

其中任一父节点都不小于子节点。
逻辑结构:
在这里插入图片描述
储存结构:

在这里插入图片描述

小根堆

其中任一父节点都不大于子节点。
逻辑结构:
在这里插入图片描述
储存结构:
在这里插入图片描述

2.实现

2.1struct Heap、HeapInit、HeapDestroy

堆的结构体、堆的初始化、堆的销毁。
从上面的大小根堆可以看出,在实现上是一个顺序表,而逻辑上是二叉树
因此,这几步与顺序表几乎完全一致:

typedef int HeapDataType;
typedef struct Heap
{
	HeapDataType* a;
	int size;
	int capacity;
}Heap;

void HeapInit(Heap* php)
{
	assert(php);
	php->a = (HeapDataType*)malloc(sizeof(HeapDataType) * 4);
	if (!php->a)
	{
		perror("HeapInit::malloc");
		return;
	}
	php->size = 0;
	php->capacity = 4;
}

void HeapDestroy(Heap* php)
{
	assert(php);
	free(php->a);
	php->a = NULL;
	php->capacity = php->size = 0;
}

2.2HeapPush

插入元素。

void HeapPush(Heap* php, HeapDataType x)
{
	assert(php);
	if (php->size == php->capacity)
	{
		HeapDataType* tmp = (HeapDataType*)realloc(php->a,sizeof(HeapDataType) * php->capacity * 2);
		if (!tmp)
		{
			perror("HeapDataType::realloc");
			return;
		}
		php->a = tmp;
		php->capacity *= 2;
	}
	php->a[php->size] = x;
	php->size++;
	AdjustUp(php->a,php->size-1);
}

先判断是否需要扩容,再插入。
但这是个堆,因此需要对数据进行调整。
此处以大根堆为例,使用向上调整法AdjustUp

AdjustUp

大根堆需要满足其中任一父节点都不小于子节点。
当插入一个节点后,可以将其与父节点比较,不满足条件则交换,最多调到根。

void AdjustUp(HeapDataType* a, int child)
{
	assert(a);
	int parent = (child - 1) / 2;
	while (parent >= 0)
	{
		if (a[child] > a[parent])
			Swap(a + child, a + parent);
		else
			break;
		child = parent;
		parent = (child - 1) / 2;
	}
}//有小bug

由公式得:父节点下标parent = (child - 1) / 2
进入循环:

  • 如果不满足条件,则交换Swap
void Swap(HeapDataType* a,HeapDataType* b)
{
	assert(a && b);
	HeapDataType tmp = *a;
	*a = *b;
	*b = tmp;
}

这里又封装了一个函数,因为这部分需要用到交换的地方还挺多的。

  • 如果满足条件,由于堆的性质,可直接break结束循环。

一个小bug:需注意的是循环结束条件parent >= 0,当运行时,会发现程序能跑,没出问题。
但可以分析一下:如果child已经为根,即child == 0,算一下parent(0 - 1)/2 = 0
下图的情景出现了!
在这里插入图片描述
当然,这里的bug是能改的,用child作为判断条件即可:

//while (parent >= 0)
while(child>0)

2.3HeapPop

删除堆顶元素。

堆顶元素为堆的最大/小值,因此,删除堆顶元素才具有一定意义。

挪动删除,
是不行的:
在这里插入图片描述
两个原因:

  • 效率低下
  • 父子关系被打乱

正确方法:

void HeapPop(Heap* php)
{
	assert(php);
	assert(!HeapEmpty(php));
	Swap(php->a, php->a + php->size - 1);
	php->size--;
	AdjustDown(php->a, php->size, 0);
}

这里,先将最前的与最后的交换(Swap),
然后删除最后一个元素(size--),
最后,向下调整(AdjustDown)。
在这里插入图片描述

AdjustDown

void AdjustDown(HeapDataType* a, int n, int parent)
{
	assert(a);
	int child = parent * 2 + 1;
	while (child < n)
	{
		if ((child + 1) < n && a[child] < a[child + 1])
			child++;
		if (a[parent] < a[child])
			Swap(a + parent, a + child);
		else
			break;
		parent = child;
		child = parent * 2 + 1;
	}
}

这里传了三个参数,其中parent为需要调整的父节点的位置,此处传0
while循环,会在child下标小于堆大小n的情况下继续执行,这表示还有子节点存在。
由于是大根堆,向下调整时,先选出子节点中的较大值,再与父节点比较,满足则break出循环,不满足条件则交换,继续循环。

2.4HeapTop、HeapSize、HeapEmpty

取堆顶元素、堆的大小、判空。

HeapDataType HeapTop(Heap* php)
{
	assert(php);
	return php->a[0];
}

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

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

3.应用

3.1堆排

即使用堆进行排序。
给一个数组,要使用堆排,前提是必须有个堆,因此第一步为建堆。
那么,建大堆还是小堆?怎么建堆?
建什么堆,这里有个规律:

  • 升序建大堆
  • 降序建小堆

如何建堆,有两种方法:

  • 使用向上调整法,插入建堆
  • 使用向下调整法,调整建堆

建堆

向上调整法

void HeapSort(int* a, int n)
{
	//建堆
	for (int i = 0; i < n; i++)
	{
		AdjustUp(a, i);
	}
	//排序
}

向下调整法
使用向下调整法建堆,需满足左、右子树必须是堆
随便给的数组肯定不能满足此条件,因此不能从根节点开始建堆,需要找倒数第一个非叶子节点:
在这里插入图片描述
叶节点是堆,空节点也是堆,因此,从第一个非叶子节点开始使用向下调整法,不断调整直到根节点。

void HeapSort(int* a, int n)
{
	//建堆
	for (int i = (n - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(a, n, i);
	}
	//排序
}

在实际使用中,通常使用向下调整法建堆,因为向上调整法的时间复杂度为O(N*logN),而向下调整法的时间复杂度为O(N)

排序

这里使用的是堆删除的思想来排序。
现在假设排升序,并且已经建好了一个大堆:
在这里插入图片描述
在这里插入图片描述
Pop一下,最大的就跑最后去了,然后重复此过程,就能排成升序。
这也验证了:

  • 升序建大堆
  • 降序建小堆

完整代码:

void HeapSort(int* a, int n)
{
	//建堆
	for (int i = (n - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(a, n, i);
	}

	//排序
	int size = n;
	while (size--)
	{
		Swap(a, a + size);
		AdjustDown(a, size, 0);
	}
}

3.2TopK问题

即在很多数中找出最…K个 数。
这里的很多一般是真的很多,因此不能在对全部数据进行排序,因为浪费空间。
解决方法是用这些数的前K个建个堆,在将其余满足条件的数插入堆中。

建堆:

  • 求最大,建小堆
  • 求最小,建大堆

插入:

依次将其余的数与堆顶的数进行比较,根据需要进行替换,最后堆中的K个数即为所求。


希望本篇文章对你有所帮助!并激发你进一步探索数据结构的兴趣!

本人仅是个C语言初学者,如果你有任何疑问或建议,欢迎随时留言讨论!让我们一起学习,共同进步!

相关文章:
数据结构-二叉树-基础知识

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

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

相关文章

手把手教你使用亚马逊云服务器创建EC2实例

陈老老老板&#x1f934; &#x1f9d9;‍♂️本文专栏&#xff1a;生活&#xff08;主要讲一下自己生活相关的内容&#xff09;生活就像海洋,只有意志坚强的人,才能到达彼岸。 &#x1f9d9;‍♂️本文简述&#xff1a;如何使用亚马逊云服务器创建EC2实例。 &#x1f9d9;‍♂…

CGAL 概念模型及Traits 概述

CGAL 概念模型及Traits 本节释了概念Concepts 、模型Models以及Traits类的含义。 CGAL Concepts and Models 概念Concepts是对类型的一组要求&#xff0c;即它具有特定的嵌套类型、特定的成员函数或具有特定的以该类型为参数的自由函数。概念的模型 Models是一个满足概念需求…

大厂最爱问的MVCC,到底是个啥?

引言 多版本并发控制&#xff08;MVCC&#xff09;是一种用于提高数据库并发性能的技术&#xff0c;尤其在处理高并发读写操作时极为有效。MVCC通过维护数据的多个版本来避免读写冲突&#xff0c;使得读操作无需阻塞写操作&#xff0c;写操作也不会影响读操作。下面&#xff0…

内网环境使用Docker部署Qwen2模型-vLLM篇

在此之前&#xff0c;我们已成功利用Docker与Ollama框架&#xff0c;在内网环境中部署了Qwen2模型。下面我们再来看一下使用Docker与vLLM框架部署Qwen2模型。 准备vLLM镜像 在一台具备网络环境的机器上执行以下命令&#xff0c;拉取vLLM的镜像&#xff1a; # 官方镜像 docke…

探索MySQL数据查询语言的无限魅力:精准检索,驾驭数据海洋的钥匙

作者简介&#xff1a;我是团团儿&#xff0c;是一名专注于云计算领域的专业创作者&#xff0c;感谢大家的关注 座右铭&#xff1a; 云端筑梦&#xff0c;数据为翼&#xff0c;探索无限可能&#xff0c;引领云计算新纪元 个人主页&#xff1a;团儿.-CSDN博客 目录 前言&#…

可视掏耳勺真的有作用吗?测评热门可视掏耳勺

随着现代人对个护健康的日益关注&#xff0c;可视掏耳勺这掏耳神器逐渐风靡市场&#xff0c;但同时也伴随着部分质疑的声音&#xff0c;甚至被贴上“智商税”的标签。因为有不少消费者使用时出现画质低清、材质不舒服等现象&#xff0c;那么&#xff0c;可视掏耳勺真的好用吗&a…

上海亚商投顾:沪指再创阶段新低 全市场下跌个股超4400只

上海亚商投顾前言&#xff1a;无惧大盘涨跌&#xff0c;解密龙虎榜资金&#xff0c;跟踪一线游资和机构资金动向&#xff0c;识别短期热点和强势个股。 一.市场情绪 沪指9月6日冲高回落&#xff0c;午后续创调整新低&#xff0c;创业板指跌1.7%。券商、保险等金融股逆势走强&a…

快速入门游戏领域,开发游戏需要哪些技术?

在这个充满创意和技术的时代&#xff0c;游戏行业成为众多创新人才追求梦想的热土。对于准备踏入这个充满挑战与机遇的领域的新人来说&#xff0c;了解游戏开发流程是至关重要的。 游戏市场蓬勃发展&#xff0c;游戏行业未来行情可观&#xff0c;在这个充满创意和技术的时代&a…

Swiper轮播图框架【前端 24】

Swiper轮播图框架 在如今的网页设计中&#xff0c;轮播图已成为一种不可或缺的元素&#xff0c;它能够以动态的方式展示图片、视频或文本信息&#xff0c;有效吸引用户的注意力并提升页面的互动性。在众多轮播图实现框架中&#xff0c;Swiper以其高度的可定制性、流畅的滑动效果…

浏览器百科:网页存储篇-如何在Chrome中打开IndexedDB窗格(十一)

1.引言 在现代Web开发中&#xff0c;网页存储技术扮演着至关重要的角色。IndexedDB作为一种低级API&#xff0c;允许客户端存储大量结构化数据&#xff0c;并提供高性能的搜索能力。在上一篇文章中&#xff0c;我们深入探讨了IndexedDB的基础知识及其应用场景。为了更有效地调…

回收玻璃减薄中的氢氟酸

回收玻璃减薄中的氢氟酸是一个重要的环保和资源再利用环节。在玻璃减薄过程中&#xff0c;氢氟酸作为主要的化学蚀刻剂&#xff0c;与玻璃基板表面的二氧化硅等成分发生反应&#xff0c;实现玻璃的减薄。然而&#xff0c;随着反应的进行&#xff0c;氢氟酸的浓度会逐渐降低&…

爆改YOLOv8|利用BiFPN双向特征金字塔改进yolov8

1&#xff0c;本文介绍 BiFPN&#xff08;Bidirectional Feature Pyramid Network&#xff09;是一种增强特征金字塔网络&#xff08;FPN&#xff09;的方法&#xff0c;旨在改善多尺度特征融合。BiFPN的主要创新点包括&#xff1a; 双向特征融合&#xff1a;与传统FPN仅在自下…

AI智能工牌:告别手动录入,1小时上门服务报告,3分钟生成

在当今快速发展的商业环境中&#xff0c;提高工作效率和客户满意度成为了企业追求的核心目标之一。传统的手动录入方式不仅耗时耗力&#xff0c;而且容易出错&#xff0c;特别是在上门服务行业&#xff0c;如何快速准确地完成服务报告成为了一个亟待解决的问题。AI智能工牌的出…

从零到精通:亚马逊和Target自养号测评的下单支付与fang关联技巧

在跨境电商的广阔领域里&#xff0c;自养号测评策略已崛起为众多卖家实现低成本、高效市场推广的一把利器。然而&#xff0c;要驾驭好这一策略&#xff0c;确保其成功实施&#xff0c;关键在于精准把握并满足一系列核心条件。接下来&#xff0c;我们将深入剖析这些条件&#xf…

IEEE投稿模板翻译

>将这一行替换为您的稿件id号(双击此处编辑)< IEEE 期刊和会议论文的撰写准备&#xff08;2022&#xff09; 第一作者 A. 作者&#xff0c;IEEE成员&#xff0c;第二作者 B. 作者&#xff0c;第三作者 C. 作者 Jr.&#xff0c;IEEE成员 摘要—本文档为IEEE会刊、期刊和…

《Neon程序员指南》文档中关于矩阵转置的两处笔误

今天在看《Neon程序员指南》&#xff08;Neon Programmer Guide for Armv8-A Coding for Neon&#xff09;发现两处笔误&#xff0c;随手记在这里。 图6-11中&#xff0c;左边的指令应该是trn1 v1.4s, v0.,4s, v3.4s。 图6-15中trn1的图中有两个箭头画错了。

漏洞挖掘 | 记一次edu通过奖学金名单泄露学号的横向渗透

0x1 前言 哈喽&#xff0c;师傅们&#xff01; 这篇文章主要是给师傅们分享下一个简单的手法&#xff0c;通过打edu的时候&#xff0c;我们可以在一些学生管理登录后台&#xff0c;需要我们使用账号是学号登录的系统&#xff0c;然后我们可以尝试通过去网上找公开的奖学金名单…

JAVA在线教育新利器高效答题系统小程序源码

在线教育新利器——高效答题系统 &#x1f680;【开篇引入&#xff1a;教育新风尚】&#x1f680; 在这个快节奏的时代&#xff0c;学习不再局限于教室的四角&#xff0c;在线教育如雨后春笋般蓬勃发展。而今天&#xff0c;我要给大家揭秘一款在线教育的新宠儿——高效答题系…

传统CV算法——threshold阈值算法介绍

阈值化函数我的理解为&#xff0c;在计算机图像视觉中&#xff0c;我们常见的RGB图像表现的信息过多&#xff0c;可能会存在于一些掺杂的噪声&#xff08;因为针对视觉目标不是我们需要的&#xff09;&#xff0c;因此使用阈值算法&#xff0c;直接效果就是可以降噪&#xff0c…

微信公众号获取 openid: 从零到一快速实现一个微信公众号授权项目

一. 前言 上一篇文章说到&#xff0c;微信官方团队发了一则公告&#xff0c;美其名曰&#xff1a;“为了优化开发体验&#xff0c;避免多个同一功能接口对开发者造成困扰&#xff0c;微信团队将对下发统一消息接口进行如下调整。” 正是由于这个调整&#xff0c;而将部分开发者…