【数据结构】长篇详解堆,堆的向上/向下调整算法,堆排序及TopK问题

[数据结构]堆的经典——TopK问题与堆排序 详细讲解堆中经典的TopK问题与堆排序以及实现源码 阅读详情

堆的概念

堆就是将一组数据所有元素按完全二叉树的顺序存储方式存储在一个一维数组中,并满足树中每一个父亲节点都要大于其子节点称为大堆(树中每一个父亲节点都要大于其子节点称为小堆)。

性质

①对于大堆(大根堆)来说,堆的顶部也就是数组首元素一定是最大的元素
②对于小堆(小根堆)来说,堆的顶部也就是数组首元素一定是最小的元素
(这两点对于下面的堆排序来说十分重要)

此外,堆总是一棵完全二叉树,因为堆本身就是二叉树的一种顺序存储结构的实现模式
注意的是这里的堆和操作系统虚拟进程地址空间中的堆是两回事,一个是数据结构,一个是操作系统中管理内存的一块区域分段

图解

在这里插入图片描述

在这里插入图片描述
通过图再去对比上面的概念和性质,理解会更加清晰

所谓的存储结构也就数据在内存中真实的存储情况,在一维数组中
而逻辑结构就是我们想象出来的,能够帮助我们理解并且通过这个也是根据二叉树中父节点和子节点之间的下标关系来确定的

①已知父亲节点求子节点

LeftChild = Parent * 2 + 1; //左孩子的节点下标
RightChild = Parent * 2 + 2; //右孩子的节点下标

②已知子节点求父节点

Parent = (Child - 1) / 2;  //切记是减1之后再除以2

向上调整算法

向上调整算法主要在堆的插入和堆排序中应用最为广泛

算法分析

对于堆的插入,就是在数组的末尾进行数字的插入,并且在插入数据之后,我们仍要保证现有的结构仍然是一个堆!
在这里插入图片描述
如上图,是一个小堆

然后在数组的末尾插入了一个数字,即最后一个孩子节点,但是在插入之后,我们自身的堆结构发生了变化,所以我们必须对堆的结构进行调整.

不难发现,在最后插入一个数之后,其他子树仍然保持了小堆的性质(即父节点的值小于子节点),而正在需要调整的就是该子节点的’祖宗’这条线路,如上图红色箭头一步一步指向的位置,
而利用的公式就是Parent = (Child - 1) / 2;把新插入的数和它的父节点作比较,如果这个新插入的数小于于父节点,那么就和父节点交换位置

在向上调整代码中,我们需要传入的参数是数组和插入的那个子孩子的节点的下标

void AdjustUp(HeapDataType* a, int child)  //child是下标

在实际的不断向上调整中,我们需要用循环来实现代码,并且要合理的设置循环

while (child > 0)   //不能设置为parent >= 0;注意这里是child>0,没有等于0,等于零就直接结束了
	{				//Parent = (Child - 1) / 2, 通过这个公式因为parent永远都不可能小于零
		...
	}

时间复杂度 -------O(logN)

根据最坏情况来看(比如上图),数据多少层,我们就需要调整多少次,所以次数=高度h
再根据二叉树节点数量和高度的关系可知:
在这里插入图片描述
所以可以得到关系: 次数 = h = logN
所以时间复杂度就为:O(logN)

代码整体实现

算法既可以实现小堆也可以大堆,具体看你函数内部符号的控制

整体实现如下:

typedef struct HeapNode
{
	HeapDataType* a;
	int size;
	int capacity;
}HP;
void Swap(HeapDataType* p1, HeapDataType* p2)
{
	HeapDataType tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
//循环写法
void AdjustUp(HeapDataType* a, int child)  //child,parent是下标
{
	int parent = (child - 1) / 2;
	while (child > 0)
	{
		//小堆:判断子节点和父亲结点的大小
		if (a[child] < a[parent])
		//大堆:if (a[child] > a[parent])
		{
			Swap(&a[child], &a[parent]);//交换孩子和父亲
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

//递归写法
void AdjustUp(HeapDataType* a, int child)
{
	int parent = (child - 1) / 2;
	if (child > 0)
	{
		//小堆:
		if (a[child] < a[parent])
		{
			Swap(&a[child], &a[parent]);
			child = parent;
			AdjustUp(a, child);  //递归
		}
		else
		{
			return;
		}
	}
	else
	{
		return;
	}
}

向下调整算法

向下调整算法主要在堆的数据删除和堆排序中应用最为广泛

算法分析

在这里插入图片描述
对于上图根节点27来说,它的左右子树都是小堆,所以就需要将27不断向下调整,保证其整体还是一个小堆
由此可见,向下调整的前提是左右子树必须是堆
利用的公式就是
LeftChild = Parent * 2 + 1;
RightChild = Parent * 2 + 2;

在每一轮的调整中你都需要比较左右子节点的大小,比如上图就是对于27来说,15和17两个节点,15更小,所以就将15和27进行交换,然后对于19这个子树来说本身就是一个小堆,就可以不用管了,并且15本身也小于19,所以也符合小堆性质,然后继续对左边的子树进行如此的调整

在向下调整代码中,我们需要传入的参数是数组,数组大小和整棵树根节点的下标

void AdjustDown(HeapDataType* a, int size, int parent)

时间复杂度 -------O(logN)

根据最坏情况来看(比如上图),数据多少层,最坏的情况我们就需要向下调整多少次,所以次数=高度h, 再根据二叉树节点数量和高度的关系可知:
在这里插入图片描述
所以可以得到关系: 次数 = h = logN
所以时间复杂度就为:O(logN)

整体代码实现

typedef int HeapDataType;

typedef struct HeapNode
{
	HeapDataType* a;
	int size;
	int capacity;
}HP;
//转换
void Swap(HeapDataType* p1, HeapDataType* p2)
{
	HeapDataType tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
//循环写法
void AdjustDown(HeapDataType* a, int size, int parent)
{
	////此处用的假设法,先假设左孩子小
	int child = parent * 2 + 1;
	while (child < size)//这里的child是左孩子下标,之所以不是child+1<size,是因为
	{			        //如果没有右孩子的话,这次循环将会终止,调整就会进行不彻底
		//小堆:
		if (child + 1 < size && a[child + 1] < a[child])
		//是否有右孩子,如果有,比较左右孩子谁大
		{
			child++;
		}

		//小堆:
		if (a[child] < a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;//交换位置
		}
		else
		{
			break;
		}
	}
}
//递归写法
void AdjustDown(HeapDataType* a, int size, int parent)
{
	int child = parent * 2 + 1;
	if (child < size) 
	{
		//小堆:
		if (child + 1 < size && a[child + 1] < a[child]) 
		{
			child++; //如果右孩子小,那么下标就换成右孩子的下标
		}
		//小堆:
		if (a[child] < a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			AdjustDown(a, size, parent);  //递归
		}
		else
		{
			return;
		}
	}
	else
	{
		return;
	}
}

堆的接口实现

接下里,我将把堆的实现过程一步一步实现出来

初始化堆

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

销毁堆

void HeapDestroy(HP* hp)
{
	assert(hp);

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

插入元素

在尾部插入之后,要用AdjustUp函数向上调整形成堆

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

	// 扩容
	if (hp->size == hp->capacity)
	{
		int new = hp->capacity == 0 ? 4 : hp->capacity * 2;
		HPDataType*tmp=(HPDataType*)realloc(hp->a, sizeof(HPDataType) * new);
		if (tmp == NULL)
		{
			perror("realloc fail");
			exit(-1);
		}

		hp->a = tmp;
		hp->capacity = new;
	}

	hp->a[hp->size] = x;
	hp->size++;

	AdjustUp(hp->a, hp->size - 1);//插入之后向上调整堆
}

删除元素

一般指删除首元素,至于为什么HeapPop是删除首元素
根本就是因为要弹出尾元素很简单,直接size–不就完了

void HeapPop(HP* php)
{
	assert(php);
	assert(php->size > 0);

	Swap(&php->a[0], &php->a[php->size - 1]);//首元素换到尾部来,然后再size--
	--php->size;

	AdjustDown(php->a, php->size, 0);//再用AdjustDown函数再来调整堆
}

打印元素

void HeapPrint(HP* php)
{
	assert(php);

	for (size_t i = 0; i < php->size; i++)
	{
		printf("%d ", php->a[i]);
	}
	printf("\n");
}

判断是否为空


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

取首元素

HPDataType HeapTop(HP* php)
{
	assert(php);
	assert(php->size > 0);
	return php->a[0];
}

实现堆

int main()
{
	HP hp;
	HeapInit(&hp);
	int a[] = { 20, 11, 28, 31, 111, 52, 34, 16, 7, 9 };
	for (int i = 0; i < sizeof(a) / sizeof(int); i++)
	{
		HeapPush(&hp, a[i]);//插入
	}
	HeapPop(&hp);//把堆的首元素7删除删除
	HeapPrint(&hp);
	printf("堆顶元素:%d\n", HeapTop(&hp));
	HeapDestroy(&hp);
	return 0;
}

运行出来的结果:
在这里插入图片描述

堆排序

首先堆排有几个关键的步骤
①创建堆 ②调整堆

创建堆

创建堆的方式有两种①向上调整建堆 ②向下调整建堆

①首先我们来看第一种:向上调整建堆

这种方式的原理就是看作最开始堆中只有一个元素,从第一个元素开始就已经在向上调整,然后逐渐像堆中加入元素,随着一个一个元素的加入,也就形成了堆
图解如下:
在这里插入图片描述
而代码就是通过AdjustUp函数和一个for循环就可以完成上面步骤

//以前n个数建小堆
for (int i = 0; i < n; ++i)
{
	AdjustUp(a, i);  //a为数组的指针
}

时间复杂度: O(n*logn)

分析:首先我们上面详细分析了AdjustUp()的时间复杂度为O(logn),然后循环了n此每次建堆,所以两者相乘,时间复杂度也就是 n*logn 了


②我们来看第一种:向下调整建堆 -----堆排序中最主要用到的方法

这种建堆的关键就是从倒数第一个非叶子节点开始调(也就是树中最后一个父节点),然后逐渐+1,就可以调整从最后一个父节点开始的每一棵树.
不难发现这样也符合向下调整的前提,即左右子树都是堆
那么我们如何找到最后一个节点的父亲?
就需要用到公式:Parent = (Child - 1) / 2;
图解如下
在这里插入图片描述

而代码就是通过AdjustDown函数和一个for循环就可以完成上面步骤

for (int i = ((n-1)-1)/2; i >= 0; --i) 
//(n-1)是拿到树最后一个节点,然后再根据公式Parent = (Child - 1) / 2;
{
	AdjustDown(a, size, i);
}

时间复杂度:O(n)
根据下面的思路
在这里插入图片描述
在这里插入图片描述

因此建堆的时间复杂度为O(n)

总结😗:其实两种方式建堆之所以时间复杂度有差距,就是因为向下调整建堆可以看作忽略了最后一排的节点,直接从倒数第二排节点开始调整的,而在一棵满二叉树中最后一排的节点其实就占据了整棵树的二分之一,所以相当于向下调整比向上调整少经历了很多的节点
所以实际堆排序中我们更多的使用的是向下调整建堆,因此时间复杂度为O(n)

还有一点需要注意的是:如果你想要升序,即从小打大,需要建大堆.
建了大堆之后,再交换首元素(最大的)和末尾元素,然后把最大的元素不算入堆中的元素,
再进行向下调整
如果你建小堆,当你拿到首元素(最小的元素之后),需要将数组依次前移然后重新建堆,每次都前移然后每次都建堆,时间复杂度直接拉满!!!
同理 如果你想要降序,即从大打小,需要建小堆.


调整堆

在堆建好之后,就可以开始调整堆了,比如你是升序,即从小打大,需要建大堆.
建了大堆之后,循环N次 ,进行N次调整堆操作,每一次调整 堆得到的最大值,将此值和数组的最后一个元素进行交换,交换减小数组的长度(最后被减小的那几个值不参与堆的调整),直到最后一个元素,就完成了堆的排序.

如下图,降序—小堆, 展示了其中一个调整过程

在这里插入图片描述

整合步骤

综合建堆和调整,完整的堆排序代码就出来了

void HeapSort(int* a, int n)
{
	// 建堆 (大堆)or  (小堆)
	for (int i = 1; i < n; i++)
	{
		AdjustUp(a, i);
	}
int end = n - 1;
while (end > 0)
{
	Swap(&a[0], &a[end]);  //交换
	AdjustDown(a, end, 0); //向下调整
	--end;   //换下来的最后一个数不计入堆中
}

升序建大堆,降序建小堆很重要!

TopK问题

最后我们再来解决一个堆在实际应用中很重要的Topk问题

通常这是在数据很大的情况下才会使用到的,如世界前500强,全省高考前十等等…
因为如果数据很大,你不可能在内存中创建一个这么大的数组来装下这么多数据,所以就要用topk问题的思路
举个简单的例子:
比如你有1000个数据,你要找前100个大的数据,那么你先随便拿100个数据(无论其大小多少)建小堆,然后另外900个数据依次与堆顶的最小数据进行比较,比它大就替换,然后再调整堆,这样1000个数据都参与了对比,对比了900次,900个最小的被拿走,剩下的100个一定是最大的,再进行堆排序

接下来用文件传输数据的形式进行举例

void CreateNDate()
{
	// 造数据
	int n = 10000000;
	srand(time(0));
	const char* file = "data.txt";
	FILE* fin = fopen(file, "w");
	if (fin == NULL)
	{
		perror("fopen error");
		return;
	}

	for (int i = 0; i < n; ++i)
	{
		int x = (rand() + i) % 10000000;
		fprintf(fin, "%d\n", x);
	}

	fclose(fin);
}

void TestTopK(const char* filename, int k)
{
	// 1. 建堆--用a中前k个元素建堆
	FILE* fout = fopen(filename, "r");
	if (fout == NULL)
	{
		perror("fopen fail");
		return;
	}

	int* minheap = (int*)malloc(sizeof(int) * k);
	if (minheap == NULL)
	{
		perror("malloc fail");
		return;
	}

	for (int i = 0; i < k; i++)
	{
		fscanf(fout, "%d", &minheap[i]);
	}

	// 前k个数建小堆
	for (int i = (k-2)/2; i >=0 ; --i)
	{
		AdjustDown(minheap, k, i);
	}


	// 2. 将剩余n-k个元素依次与堆顶元素交换,不满则则替换
	int x = 0;
	while (fscanf(fout, "%d", &x) != EOF)
	{
		if (x > minheap[0])
		{
			// 替换你进堆
			minheap[0] = x;
			AdjustDown(minheap, k, 0);
		}
	}


	for (int i = 0; i < k; i++)
	{
		printf("%d ", minheap[i]);
	}
	printf("\n");

	fclose(fout);
}


int main()
{
	CreateNDate();
	TestTopK("data.txt", 5);
	return 0;
}
【数据结构】堆的拓展延伸 —— 堆排序 和 TopK问题 图文详解 + 多样例分析证明带你掌握 堆排序 和 TopK问题 阅读详情

相关推荐

用废旧汽车电池DIY家用UPS:手把手教你低成本搭建Wi-Fi不断电系统

本文详细介绍了如何利用废旧汽车电池DIY低成本家用UPS系统,确保Wi-Fi设备在断电时持续供电。通过精选材料、电路搭建和安全防护,实现高效稳定的电源转换与智能切换,成本仅为市售UPS的三分之一。特别适合家庭办公和远程工作者提升网络供电可靠性。

weixin_29200485的博客 468

堆排序(向下调整法,向上调整法详解)

普通的二叉树是不适合用数组来存储的,因为可能会存在大量的空间浪费。而完全二叉树更适合使用顺序结构存储。现实中我们通常把堆(一种二叉树)使用顺序结构的数组来存储,需要注意的是这里的堆和操作系统虚拟进程地址空间中的堆是两回事,一个是数据结构,一个是操作系统中管理内存的一块区域分段。

走在努力路上的自己的博客 3231

私彩视频开奖动画直播源码内部核心技术解析及修改可增加后台操控-持续更新文章!

其实在此很感谢能做出H5开奖系统的码农们,而我本人也喜欢分解此类的东西。 因为特殊原因,该东西本人就不做图片展示,以防他人商业化。 借用开奖摇奖代码首先分解首页index文件。代码如下:(后面持续在原有的作品上添加后台操作) 代码里写的//.....是我的注明代码的所需。 ​ &lt;!DOCTYPE html&gt; &lt;html&gt; &lt;head&gt; &lt;...

yugua3837的博客 5997

优先级队列(大根堆与小根堆)

优先级队列(PriorityQueue)其实就是所谓的堆,堆是一颗完全二叉树下的一种形态。存储数据在堆中为何是完全二叉树呢?是这样的,使用完全二叉树时可以避免空间的浪费,这也是堆的存储模式特别之处,如果是一些不连续的数组,采用顺序表存储更好。堆的使用:这是最简单的创建一个堆,我们可以看见,堆的特别之处是可以用于排序,所以对于排序的几种方法中,就有堆排序这种方案,Java中提供的默认堆是大根堆。堆分为两种,大根堆和小根堆。

闲来垂钓碧溪上的博客 4002

Halcon手眼标定实战:从九点标定到机械臂精准抓取(附完整代码)

本文详细介绍了Halcon手眼标定技术在实际工业场景中的应用,重点讲解了九点标定法在机械臂精准抓取中的实现步骤和优化技巧。通过对比Eye-to-Hand和Eye-in-Hand两种模式的特点,提供完整的代码示例和误差分析,帮助工程师快速掌握标定技术,提升自动化系统的精度和稳定性。

weixin_29100927的博客 460

SQL面试题——最大连续登陆问题

最大连续登陆是我们计算比较常用的指标,我们可以将其当成一个经典的SQL模型,后面我们也会陆续总结出其他的模型,你还知道其他的比较经典的SQL 吗?欢迎分享,或者你还知道其他的关于计算连续的指标?最大连续登陆重要的是计算思想,最大连续才是关键,登陆只是一个场景,要学会举一反三。

9013

数据结构 | 堆的实现 向上调整算法 向下调整算法 堆排序

本次我们学习到了堆的相关知识,堆本质上就是一个数组。然后我们又学习了【向上调整算法】和【向下调整算法】,知道了它们的时间复杂度【向上调整算法】它的时间复杂度为O(NlogN),【向下调整算法】它的时间复杂度为O(N);所以平时要优先使用向下调整算法。接着我们又学习了堆排序,它是八大排序算法之一,它的时间复杂度为O(NlogN);最后我们学习了TOP-K问题–即求数据结合中前K个最大的元素或者最小的元素。以上就是本文的全部内容了,若有问题请在评论区留言,觉的好的话就留下你的三连吧。

2301_77412625的博客 1165

TPAMI 2024 | 面向大型城市场景的快速建筑实例代理重建

大规模城市场景(特别是建筑物)的数字化是一个长期存在的开放问题,这归因于数据获取方面的挑战,如场景覆盖不完整、缺乏语义、规划路径的效率和可靠性低。在本文中,我们针对从航拍图像中重建城市建筑的问题,提出了一个有效的工作流程和一些新颖的算法,用于大型城市场景中高效的3D建筑实例代理重建。具体来说,我们提出了一种基于学习的新型方法,用于从航拍图像中进行城市建筑的实例分割,然后通过基于投票的算法将多视图实例信息融合到稀疏点云中(使用标准结构从运动管道重建)。我们的方法实现了从点云中对建筑实例进行有效的实例分割。

小白学视觉 322

【数据结构】【堆】【c】详解堆的自顶向下算法、和堆的建立。

最近笔者花了几天时间研究了堆,下面做一些分享。 首先什么是堆。 堆在逻辑上是特殊的完全二叉树,在存储结构上是顺序表。一般用数组实现。 堆只分为大堆和小堆 所谓大堆是指:在堆中所有的最小二叉树的父亲节点必须比它的叶子节点大。 所谓小堆是指:在堆中所有的最小二叉树中父亲节点比它的叶子小。只有这两种特殊的情况下一个数组才能被称为堆。 堆的自顶向下算法: 现在我们给出一个数组,逻辑上看...

mysterious superstar 3042

YOLOv11改进策略【卷积层】| ICCV-2023 引入Dynamic Snake Convolution动态蛇形卷积,改进C3k2

基于拓扑几何约束的动态蛇卷积用于管状结构分割DSConv(Dynamic Snake Convolution,动态蛇形卷积)模块的设计主要是为了更好地处理管状结构的分割任务,解决传统卷积在处理细管状结构时的不足。

Limiiiing的博客 1629

双向升压和降压的DC-DC转换器设计

c复制// 类型定义与常量MODE_FORWARD_BOOST, // 正向升压模式(输入700V→输出1200V)MODE_FORWARD_BUCK, // 正向降压模式(输入700V→输出<700V)MODE_REVERSE_BOOST, // 反向升压模式(输入1200V→输出700V)MODE_REVERSE_BUCK // 反向降压模式(输入<700V→输出700V)// 硬件参数float Vin;// 输入电压(0-1200V)

568

堆的向上与向下调整

本文深度剖析堆的向上调整与向下调整。推导其时间复杂度。

努力进大厂的小码娄 1351

harbor x509: certificate signed by unknown authority【20251222】

本教程通过「合并证书链(服务器端)+ 预置根 CA(客户端)」彻底解决了自签名证书的 x509 错误,同时集成了 Trivy 漏洞扫描,实现了 Harbor 从搭建到实用的全流程。证书链合并是服务器端的关键,确保客户端获取完整信任路径;客户端需同时配置「系统信任库 + Docker 信任目录」,避免 Docker 独立信任机制导致的问题;所有 IP 需保持一致(Harbor 配置、证书绑定、客户端访问),是避免错误的基础。

老韩的Linux云计算架构师进阶之路 682

【数据结构】堆排序与TopK问题

本篇文章博主主要围绕堆这一数据结构展开,内容包括两种建堆方式,两种建堆方式的时间复杂度分析,最后引入堆的应用:堆排序和TopK问题,希望大家多多点赞收藏支持🔥

℉f的博客 1744

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

手撕堆的实现(堆排序,Topk问题)——单手吊打数据结构

带你一命通关堆的实现; 分分钟手撕堆排序; 单手吊打数据结构!

qq_61500888的博客 2149

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

解密堆排序与TopK问题

在二叉树的存储结构中提到堆可以进行排序,也就是今天的主题堆排序,堆的排序还可以解决Topk问题,今天我就向大家解密什么是堆排序和Topk问题,它们的原理又是什么。Top-K问题是指从一组元素中找出前K个最大(或最小)的元素。这个问题在数据处理和算法设计中经常遇到,常见的应用场景包括搜索引擎中的搜索结果排序、推荐系统中的物品推荐、数据分析中的数据筛选等。找出前K个最大的元素。

2202_75605090的博客 1486

ssm框架搭建

这个是一个在idea上成功跑起来的ssm框架项目,保存在这里方便以后使用直接下载

一篇解建堆,堆的实现,堆排序,TopK问题(C语言)《数据结构与算法》

这里作者是建的小堆,如果要建大堆就改作者在代码里面声明的地方,就把三个小于改成大于就可以了哈。源代码里面解决了:建堆(给一个数组建堆),堆的各项操作,堆排序,TopK问题。PS:代码里面有详细的解释哈。main函数里面test1里面是建堆的各项操作的测试及堆排序的测试。 test2里面是给一个数组然后建堆的测试哈。

原来45的博客 1609

【数据结构与算法】堆排序、TopK问题详解

一、堆排序 二、TopK问题 TopK问题的测试

重铸自身根基的博客 1157

【数据结构指南】堆排序和TopK

处理数据时总被 “排序耗时长” “海量数据找 TopK 占内存” 难住?堆数据结构正是破局关键!本文围绕堆的两大核心应用堆排序和TopK问题展开,从基础建堆到实战落地,用通俗解析 + 实例让复杂问题变简单。

一枚菜鸟程序员的飞行之路 2065

堆的实现+堆的应用(堆排序和Topk)

又是一万字的堆啊,你喜不喜欢

adcxhw的博客 1405

堆特别篇:堆排序_TopK算法问题

堆特别篇:堆排序_TopK算法问题

ChrisJamesljl的博客 2584

八大排序算法之堆排序的实现+经典TopK问题

堆排序与TopK问题

小青菜的博客 3046

【数据结构】堆——堆排序与海量TopK问题

上一篇我们学习了堆的数据结构,现在我们来看看堆的日常应用和排序建堆的时间复杂度是O(N),排序的时间复杂度是,整体是,具体怎么算出来的大家可以看下面内容。建堆需要从倒数第一个非叶子结点开始调整,从后往前,直到堆顶的数据最后一个非叶子结点的计算方式:n - 1是最后一个节点的下标,父亲 = (孩子 - 1) / 2,所以(n - 1 - 1) / 2end是最后一个数据的下标,我们先交换,在向下调整,就传过去了end(n-1)个要调整的数据,正好排除掉了最后已经排序好的数据。end > 0。

Eraser的博客 1500

堆和堆排序【数据结构】

堆(Heap)是一种数据结构,它是 **一种特殊的二叉树** ,其中父节点的键值总是大于或等于(或小于或等于)其任何一个子节点的键值。这意味着在堆中,根节点具有最大(或最小)键值。

加油!!! 1713

数据结构:堆 的详解

堆 文章目录堆堆的概念及结构堆的性质堆的实现向下调整算法(小根堆)代码向上排序算法代码堆的创建堆的增加元素堆减少元素堆的应用堆排序TopK问题 堆的概念及结构 如果有一个关键码的集合k={k0,k1,k2…,kn-1},把他们的所有元素按完全二叉树的顺序存储方式存储在一个一维数组中,并满足:Ki<K(2i+1) 且 Ki<=K(2i+2) (或Ki>=K(2i+1) 且Ki>=K (2i+2) ) i=1,2,…,则称为小堆(大堆)。将根节点最大的堆叫做对大堆或大根堆,根节点最小

潜心钻研技术 7671

【数据结构】树与堆 (向上/下调整算法和复杂度的分析、堆排序以及topk问题)

向上/下调整算法和复杂度的分析、堆排序以及topk问题

weixin_69380220的博客 1599
上一篇: 【数据结构】树的基础入门
下一篇: 【排序算法】详解冒泡排序及其多种优化&稳定性分析
kklovecode
博客等级 码龄4年 127粉丝 29原创
评论 22
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

打赏作者

kklovecode

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值