【数据结构】堆排序与Top-K问题

【数据结构】堆排序和Top-K问题(超详细) 堆排序和TopK 阅读详情

在上篇文章了解了堆的基本功能后,我们便可以使用堆来实现其独特的功能——堆排序与Top-K问题,我们一起来往下学习。

目录

一、 堆排序(test)的打印

二、 堆排序的插入

三、 堆排序

3.1 堆排序的实现

3.2 堆排序的过程

3.3 堆排序的注意事项

3.4 向下调整建堆的效率

3.5 向上调整建堆的效率

四、 Top-K问题

4.1 Top-K问题是什么

4.2 Top-K的实现

4.3 检测Top-k


一、 堆排序(test)的打印

基于上篇文章的学习,我们发现,如果是小堆,那堆顶的数据一定是最小的,那这样我们就可以每次打印出堆顶的元素,然后弹出堆顶的元素(最小的元素),一直保持堆顶的元素是堆中最小的,这样,就可以将堆中的数据从小到大排序。

这样就可以将一个数组中的元素进行排序。

不难发现,我们这样排序的空间复杂度:O(N),时间复杂度:O(N*logN)。

但是我们一般排序都是将数组中的元素进行排序,而不是将其打印出来,接下来我们看看如何将数组中的值排序吧。

二、 堆排序的插入

思想:

①以建小堆的方式,将数组中的值不断放入到堆中(建堆)。

②然后将堆顶的元素放回到数组中,然后弹出堆顶元素,直到堆为空,这样数组中就的值就有序了。

void HeapSortInsert(int* a, int n)
{
	//升序:建小堆,每次弹出堆中最小的元素
	Heap hp;
	HeapInit(&hp);
	//  将传入的a数组中的值,插入到堆中
	for (int i = 0; i < n; i++)
	{
		HeapPush(&hp, a[i]);
	}
	int i = 0;
	printf("\n升序:\n");
	//再将堆顶的数据依次插入到数组中   直到堆为空
	while (!HeapEmpty(&hp))
	{
		a[i++] = HeapTop(&hp);
		HeapPop(&hp);
	}
	printf("\n");
}

我们来看看排序之后的结果。

不难发现,使用了HeapSortInsert堆排序后,数组中的元素就被排好了序。

可是此时面临两个问题:

问题1:首先要写一个堆数据结构,将问题复杂化了。
问题2:虽然时间复杂度达到了O(N * lonN),但有O(N)的空间复杂度。

这样的堆排序完全没有展现出它结构优势,所以接下来我们要实现真正的堆排序,不用额外创建一个堆的数据结构,并且空间复杂度达到O(1);

三、 堆排序

接下来算是真正的进入正题,如何实现堆排序。

3.1 堆排序的实现

思路:

①以升序为例,在原数组上建立一个大堆。

②从最后一个非叶子结点开始,使用向下调整算法开始建立大堆。

③因为堆顶元素最大,所以不断把堆顶元素放入数组末尾(即将大的沉到堆底,具体请看下面的GIF图),然后将新堆顶的数据进行向下调整,使其稳定堆的结构,直到n个数调整完成。

void HeapSort(int* a, int n)
{
    for (int i = (n - 1 - 1) / 2; i >=0 ; i--)
	{
		//n是数组大小,数组大小即sizeof(a)的大小,数组操作实际是从下标开始操作
		AdjuestDown(a, n, i);
	}
	int end = n - 1;
	while (end>0)
	{
		swap(&a[0], &a[end]); //将最大的沉到堆底.
		AdjuestDown(a, end, 0);
		end--;
	}

}

3.2 堆排序的过程

 我们排序这个数组来举例堆排序的实现

int main ()
{
    int a[] = { 65,17,9,53,45,23,78 };
	HeapSort(a, sizeof(a) / sizeof(a[0]));
	Print(a, sizeof(a) / sizeof(a[0]));
    return 0;
}

接下来我们来看看堆排序是如何将大堆的结构进而转化为升序的。 

 排完之后,我们将其输出:

这时,堆排序就实现了,此时,不管是时间复杂度O(N*logN),还是空间复杂度O(1)都达到了极限。

并且堆排序的只需要一个向下调整算法即可(AdjustDown),实现起来也没那么复杂了。

3.3 堆排序的注意事项

这里有四个注意的点:

①实现堆排序必须先完成建堆的过程,堆排序的建堆是在数组自身中建队,而在建堆过程中我们使用向下建堆的时间复杂度是O(N),向上调整建堆的空间复杂度是0(N*logN),具体推导过程我放在下面。

②向下调整建堆要从倒数第一个非叶子节点开始调整。即从最后一个节点的父亲开始调整。

计算那个结点的方式是:n-1是算出最后一个节点的下标,然后套规律  父亲=(孩子-1)/2。 所以是(n-1-1) /2 。

③调用向下调整时的注意点:我们使用end表示堆未排序的最后一个元素的下标,在我们进行向下调整时,传入的调整数组大小是end,传入end+1则是整个数组。因为我们不想让刚下来的堆顶元素被调换,其下标是end,所以传入数组大小为end,最多调换到end-1下标处的元素,正好避免此元素被调整。

④要控制调整的次数。即while的条件是(end>0),在end == 0时结束调整,因为当end为1时,end与堆顶元素即a[0]调整,如果不控制end == 0则会出现自己与自己调换,很明显这是不符合的。

3.4 向下调整建堆的效率

3.5 向上调整建堆的效率


四、 Top-K问题

4.1 Top-K问题是什么

Top-K问题:即求数据结合中前K个最大元素或最小的元素,一般情况下数据量比较大。

比如:专业前10名、世界500强、富豪榜、游戏中前100名活跃玩家

对于Top-K问题,能想到的最简单直接的方式就是排序,但是:如果数据量非常大,排序就不太可行了(数据量太大无法全部加载到内存中),这时最佳的解决方案就是使用堆来解决。

4.2 Top-K的实现

思路如下:

①用数据集合中前K个元素来建堆

        前k个最大的元素,建小堆

        前k个最小的元素,建大堆

②用剩余的N-K个元素依次与堆顶元素来比较,不满足则替换堆顶元素

将剩余N-K个元素依次与堆顶元素比完之后,堆中剩余的K个元素就是所求的前K个最小或最大的元素。

//求n个数中的前k的数,建小堆。
void PrintTopK(int* a, int n, int k) {
	// 一、 建堆--用a中前k个元素建堆
	//先建立一个k个数的数组,有时可能会要求返回此数组,所以要使用malloc开辟数组。
	int* KMinHeap = (int*)malloc(sizeof(int) * k);
	assert(KMinHeap);
		//将k个元素先直接放入到数组中
		for (int i = 0; i < k; ++i)
		{
			KMinHeap[i] = a[i];
		}
		//使用向下建堆的方式构建堆   --  类似堆排序的建堆
		for (int i = (k - 1 - 1) / 2; i >= 0; i--)
		{
			AdjuestDown(KMinHeap, k, i);
		}
	// 2. 将剩余n-k个元素依次与堆顶元素交换,不满则则替换
		for (int j = k; j < n; ++j)
		{
			if (a[j] > KMinHeap[0])
			{
				KMinHeap[0] = a[j];
				AdjuestDown(KMinHeap, k, 0);
			}
		}
		//打印堆中的数据
		for (int i = 0; i < k; ++i)
		{
			printf("%d ", KMinHeap[i]);
		}
}

以上是以减小堆来举例,同样有几个注意的点。

1. 开始是直接将k个数放入到Top-k数组中,好比是创建了一个数组,然后使用向下调整,将其变为了小堆。

2.最大的K的数,应建小堆,因为此时堆顶的数据是Top-k数组中最小的,我们拿剩下的n-k个数与堆顶数据比较。如果出现比堆顶数据大的,则要放入堆,说明这个数是前K个,再向下调整到合适的位置。一直循环,直到遍历完这个数据集合。

4.3 检测Top-k

思路:

创建一个1000个数的数据集合,其中每个数都小于1w,然后让10个数大于1w,将这个数据集合传入函数中,让其打印出这10个元素,即可检测我们实现的Top-K是否有问题。

测试代码:

void TestTopk()
{
	int n = 1000;
	int* a = (int*)malloc(sizeof(int) * n);
	srand((unsigned int)time(NULL));
	for (int i = 0; i < n; ++i)
	{
		//所有数取模上10000,就导致生成的数不可能超过10000,
		a[i] = rand() % 10000;
	}
	//将其中几个数设置为1w以上。
	a[5] = 10000 + 1;
	a[121] = 10000 + 2;
	a[531] = 10000 + 3;
	a[511] = 10000 + 4;
	a[115] = 10000 + 5;
	a[335] = 10000 + 6;
	a[999] = 10000 + 7;
	a[76] = 10000 + 8;
	a[423] = 10000 + 9;
	a[314] = 10000 + 10;
	PrintTopK(a, n, 10);
}

结果如下:

这样Top-K问题就实现了,时间复杂度为O(K+(N-K)*logK),空间复杂度为O(K),这是实现Top-k问题时间、空间效率都非常高的一种方法。

五、 总结部分

本篇博客实现了堆排序,一步一步优化推出堆排序,并且巧妙的解决了Top-K问题。其中有一些难得理解的地方。

比如升序应该建立什么堆,为什么升序要建立大堆,然后对这个是大堆如何操作将其变成有序序列的。

再如为什么要使用向下调整而不使用向上调整算法,以及向下调整算法为什么比向上调整算法的时间复杂度低;还有怎么实现向下调整,向下调整是从哪个结点开始调整的,如何计算那个结点;这些是堆排序需要重点理解的点。

然后是Top-K问题中取前k个数要建立什么堆,为什么要这样建立,建立完之后剩下n-k的数需要做什么,每次比较之后为什么还要进行调整,这些则是Top-K问题需要重点理解的点。

本篇内容需要一定的理解,开始可能会比较抽象,多去画图实践结合调试去观测,就会发现其规律,理解之后就会领悟其中的奥妙。

好了,本篇文章就结束了,堆的相关内容也就告一段落了,接下来会进入其他的排序的学习中,希望大家持续关注,我们下期再见。

点点关注点点赞呗,谢谢~~

MTK MT6833平台屏幕与TP驱动移植实战:从FAE伪代码到内核模块集成 本文详细介绍了在MTK MT6833平台上进行屏幕与TP驱动移植的完整实战流程。文章从解析供应商提供的初始化伪代码入手,逐步拆解了在LK阶段点亮屏幕、在内核层集成Panel驱动以支持休眠唤醒,以及移植并调试sitronix_ts触摸驱动的核心步骤与关键技巧,为嵌入式开发人员提供了从零到一集成新硬件的清晰指南。 阅读详情

相关推荐

详解Numpy中的transpose方法

最近在学Numpy碰到了关于数组转置和换轴的概念,尤其是对tanspose方法有些疑惑,尤其是多维,查了不少资料,终于搞懂了。 Numpy数组的轴(axis) 所谓的**轴(axis)**可以理解为数组的维度。 具体而言,二维数组就是有两个维度,如果从空间上看,它就是一个平面,由x轴和y轴构成,而与此相对应的,该二维数组就有两个轴,x轴对应0轴,y轴对应1轴。 同理,三维数组有三个维度,空间上...

BlankGrid的博客 938

堆的应用(堆排序、TOP - K问题)

堆的应用(堆排序、TOP - K问题)内有详细解释,欢迎各路大佬来这里品尝🥰🥰🥰

Yawesh的博客 1671

【XR硬件介绍】PICO Space Pro(Project Swan)深度解析:字节 2026 旗舰 MR 头显延期背后

内部代号 Project Swan 的 PICO 年度旗舰 MR 头显正式定名PICO Space Pro,原本计划 2026 年 9 月对外发布,官方官宣推迟至 2026 年第四季度上市。硬件层面已经完成 FCC 认证,硬件成熟度较高,延期核心目标是打磨 PICO OS 6 空间操作系统,补齐空间计算软件能力。本文从产品背景、硬件架构、软件生态、延期逻辑、行业竞争格局几个维度拆解这款国产高端 MR 设备,分析它对国内 XR 产业的意义与待解的问题。

houdou112358的博客 1230

堆排序--TOP-K问题解决及复杂度分析

堆排序算法实现,TOP-K问题解决代码及复杂度分析。

qq_43710269的博客 2017

(八)基于MuJoCo的LeRobot SO100仿真操控实战指南(操作优化)

本文详细介绍了如何在MuJoCo仿真环境中搭建并优化LeRobot SO100机械臂的操控。通过两种核心模式(纯键盘仿真与实机镜像同步)的实战演示,重点讲解了关键的参数标定方法,以实现虚拟模型与真实硬件的精准同步。文章还提供了性能优化技巧,旨在帮助开发者构建高效、安全的机器人算法开发与测试流程。

weixin_29083649的博客 319

LTC3783 LED驱动控制器设计与效率优化详解

开关电源控制器是现代LED照明的核心器件,通过电流模式控制实现高效能量转换。其核心原理是通过采样电阻检测负载电流,配合误差放大器形成闭环调节,在汽车电子、显示背光等场景中确保±3%的电流精度。LTC3783作为典型代表,集成升压/降压-升压多拓扑支持,配合PWM调光技术可实现3000:1的无色偏调光范围。在工程实践中,需重点考量功率电感选型、MOSFET开关损耗与热管理设计,例如在38W功率方案中采用10μH/5A电感和4.5mΩ MOSFET可达成95%效率。该方案特别适用于需要应对9-36V宽输入电压波

weixin_36231030的博客 400

能带你起飞的【数据结构】成王第十二篇:堆2

前言 堆的其他应用TopK 问题堆排序 java对象的比较 用代码实现TopK问题373. 查找和最小的 K 对数字 - 力扣(LeetCode) ​​​​​​​

m0_64397675的博客 322

QTabWidget动态标签页管理:从代码创建到UI设计的实战指南

本文深入探讨了QTabWidget动态标签页管理的实战技巧,涵盖从纯代码创建到UI设计器布局的两种核心方法。详细讲解了如何动态添加、插入、关闭标签页,并管理其内容与生命周期。文章还提供了标签页精细化控制、界面优化(如设置图标、样式表)以及构建简易多文档编辑器的进阶实战案例,帮助开发者灵活应对数据监控、多文档编辑等复杂界面需求。

omega的博客 866

【数据结构】堆的应用-----TopK问题

时间复杂度:建N个数的堆为O(N),获取堆顶元素 (也即是最值) 并删除掉堆顶元素为O(log2N),上述操作重复 k 次,所以时间复杂度为O(N+k*log2N)。▶ 建立 10 个元素的小堆,数据集合中前 10 个元素依次放入小堆,此时的堆顶元素是堆中最小的元素,也是堆里面第 10 个最小的元素,前面两个方法都不能用了。▶ 这样一来,堆里面存放的就是数据集合中的前 10 个最大元素,如果对堆和二叉树还不够了解的可以看看我之前的文章哦!此时小堆的堆顶元素也就是堆中的第 10 个最大的元素。

weixin_45031801的博客 1089

SAR成像系列:【3】合成孔径雷达(SAR)的二维回波信号与简单距离多普勒(RD)算法 (附matlab代码)

二维回波信号分析

zr2006_7的专栏 2万+

PDN分析实战:电源完整性设计与Altium PDN Analyzer全流程指南

电源完整性(PI)是嵌入式硬件系统稳定运行的基础能力,其核心在于控制电流密度与电压降两大物理量。基于欧姆定律与热-电耦合原理,PDN分析将抽象的电源噪声问题转化为可量化、可定位的几何与电气参数优化过程。该技术显著提升系统抗干扰能力、降低复位与ADC漂移等隐性故障率,并直接支撑EMC合规性设计。典型应用场景包括电机驱动板、Wi-Fi模块、STM32/ESP32等资源受限型MCU供电网络验证。本文以Altium Designer为工具平台,结合IPC-2152标准与真实工程案例,详解从功率预算、模型构建到瓶颈定

weixin_28949937的博客 229

数组中的第K大元素[堆排 + 建堆的实际时间复杂度]

堆排可以不用完全排序,再取第K大元素,但是选择排序也亦如此。 那么谁会运行的更快呐?建堆时,需要调整n/2次,每次最大深度为log~2~(n),第一直观印象就是建堆T(n) = O(nlgon),其实不然。

qq_43164662的博客 568

HTML满屏跳动的爱心(完整代码)

HTML语言实现满屏跳动爱心的完整代码。

AIGC 12万+

【数据结构与算法】堆排序(向下和向上调整)、TOP-K问题(超详细解读)

C数据结构的堆排序,包含了向上调整以及向下调整的知识点,还有Top-K问题。

weixin_65186652的博客 1623

VB摄像头监控

该VB模块可对摄像头捕获的画面进行录像等,可在创建的窗口进行控制

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

【数据结构与算法】之堆的应用——堆排序及Top_K问题!

向下调整建小堆,再将堆顶元素与堆的最后一个元素交换,那么数组中最小的那个元素就到最后面去了,最后一个数有序后,那我们就不再把它看作堆的元素了,最后再在堆顶进行向下调整保证小堆不变。对一个无序的数组,因为数组中的元素是连续的,那我们就可以将数组中的元素进行。2、再将后N-K个数据分别和堆顶数据比较,比堆顶数据大就将堆顶数据覆盖。4、将后N-K个数据比较完后,小堆里面的数据就是最大的前K个。假设我们要对一个数组中的元素进行降序,那我们就要先将其进行。1、我们先将全部数据的前K个建成小堆。我们下期不见不散~~

2301_80221228的博客 2019

霍尼韦尔SoftMaster使用说明

霍尼韦尔SoftMaster使用说明

【数据结构】堆的实现和堆排序--TOP-K问题

在头文件中进行堆的定义和接口的声明int size;}HP;//堆的初始化//堆的销毁//堆的插入//堆的删除//取堆顶的数据// 堆的数据个数//堆的判空堆的实现堆是一种特殊的树形数据结构,通常实现为完全二叉树。堆分为最大堆和最小堆,最大堆的父节点值大于或等于其子节点值,而最小堆的父节点值小于或等于其子节点值。堆通常使用数组来实现,通过索引关系模拟树形结构,支持高效的插入、删除和查找最大/最小元素操作。堆排序堆排序是一种基于堆的排序算法。

2301_78724721的博客 1687

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

【数据结构初阶】堆排序和TOP-K问题

堆排序是八大经典排序之一,是非常好用的算法,而我们的Top-K是我们堆排序的应用,对于我们理解和学习堆的结构有更好的帮助。

m0_70088010的博客 390

【数据结构】—堆排序以及TOP-K问题究极详解(含C语言实现)

本文为作者对于堆排序以及Top-k问题的总结,应该算比较全面的了,希望大家多多支持 _(:з」∠)_

B1ue还在专注 3216

【数据结构】堆(二)——堆排序、TOP-K问题

【数据结构】堆(二)——堆排序、TOP-K问题

一个喜欢猫咪的程序员 1151

【数据结构】堆的复杂度,堆排序和TOP-K问题

堆通常是一个可以被看作一个完全二叉树,满二叉树也可以看做一个完全二叉树,所以我们可以用满二叉树来证明。调整次数用N来表示,从倒数第二层开始调整(叶子节点不用调整)

m0_72651735的博客 1180

【数据结构】堆排序与TOP-K问题

堆排序与TOP-K问题

Yui_的博客 1238

【数据结构】堆排序及TOP-K问题

堆排序及TOP-K问题

gwPersevere的博客 370

C语言 数据结构 【堆】动态模拟实现,堆排序,TOP-K问题

堆的各个接口的实现(以代码注释为主),实现堆排序,解决经典问题:TOP-K问题

2401_88433210的博客 1163

【数据结构】堆的实现(向上、下调整比较,复杂度,堆排序,Top-K问题)

【数据结构】堆的实现(向上、下调整比较,复杂度,堆排序,Top-K问题)

m0_66599463的博客 1106

【数据结构】堆的实现,堆排序以及TOP-K问题

向堆中插入一个元素,我们可以将这个元素插入到堆的尾部,因为堆的实际存储结构是一个数组,我们可以将元素放到数组末尾,但如果仅仅是插入到数组末尾的话,会将堆的结构给破环,我们还需要调用一个向上调整的函数,来调整各个节点间的大小关系。弹出元素就是将堆顶的元素给删除,但我们不能直接进行删除,这样会将堆的结构给破坏,正确的方法是先将堆顶的元素和最后的元素进行交换,这样保证的首元素的左子树和右子树依然是堆的形态,然后将size自减,最后调用一个堆的向下调整函数。这里写的是小根堆,大根堆可以在小根堆的基础上稍作修改。

m0_73648729的博客 2549

【数据结构】堆,堆的实现,堆排序,TOP-K问题

数据结构——堆,堆的实现,堆排序,TOP-K问题

m0_62531913的博客 474
上一篇: 【数据结构】堆的基本操作
下一篇: 插入排序、希尔排序—动图演示+代码展示【C语言】
Brant_zero2022
博客等级 码龄5年 582粉丝 108原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Brant_zero2022

素材免费分享不求打赏,只求关注

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值