【数据结构】深度剖析最优建堆及堆的经典应用 - 堆排列与topk问题

这篇具有很好参考价值的文章主要介绍了【数据结构】深度剖析最优建堆及堆的经典应用 - 堆排列与topk问题。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

🚩纸上得来终觉浅, 绝知此事要躬行。
🌟主页:June-Frost
🚀专栏:数据结构

【数据结构】深度剖析最优建堆及堆的经典应用 - 堆排列与topk问题,数据结构,数据结构,建堆,堆的应用,堆排序,TOPK

🔥该文章分别探讨了向上建堆和向下建堆的复杂度和一些堆的经典应用 - 堆排列与topk问题。

❗️该文章内的思想需要用到实现堆结构的一些思想(如向上调整和向下调整等),可以在另一篇文章《堆的顺序实现》中再次了解一下,其中一些接口有具体的实现💖。

🌍 建堆

 建堆的常见方式有两种:向上建堆和向下建堆。

🔭 向下建堆

【数据结构】深度剖析最优建堆及堆的经典应用 - 堆排列与topk问题,数据结构,数据结构,建堆,堆的应用,堆排序,TOPK
 这些交换其实就是向下调整的过程,所以向下建堆只要通过不断的向下调整就可以实现。

    int arr[] = { 10,20,25,35,60,36,15 };
	int n = sizeof(arr) / sizeof(arr[0]);
	int i = 0;
	for (i = (n - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(arr, n, i);//向下调整
	}

✈️时间复杂度

【数据结构】深度剖析最优建堆及堆的经典应用 - 堆排列与topk问题,数据结构,数据结构,建堆,堆的应用,堆排序,TOPK
 将每层数据个数 * 向下移动的层数求和,得到 T(h) = 2(h-2)*1 + 2(h-3)*2+…+21 *(h-2) + 20 *(h-1) 。通过错位相减,可以得到T(h) = 2h-1-h。因为是满二叉树,所以假设节点为N,则N = 2h - 1 , h = log2(N+1) ,将h换为N,可以得到向下调整建堆合计调整次数 T(N) = N-log(N+1),所以时间复杂度为: O(N) 。


🔭 向上建堆

【数据结构】深度剖析最优建堆及堆的经典应用 - 堆排列与topk问题,数据结构,数据结构,建堆,堆的应用,堆排序,TOPK
 同样,这些交换的思想就是向上调整,通过不断调整就可以建堆。

    int i = 0;
	for (i = 1; i < n; i++)
	{
		AdjustUp(arr, i);//向上调整
	}

✈️时间复杂度

【数据结构】深度剖析最优建堆及堆的经典应用 - 堆排列与topk问题,数据结构,数据结构,建堆,堆的应用,堆排序,TOPK
将次数求和,T(h) = 21*1 +222+…+2h-2 * (h-2) + 2h-1 * (h-1)。将h与N换算,得到T(N) = -N+(N-1)(log(N+1)-1)+1 ,总的来说,时间复杂度为 O(N * logN) 。


 📙一棵满二叉树的最后一层节点数大概占据总数的50%,向上建堆在最后一层非常吃亏,不仅节点多,调整次数也多,而向下建堆避开了最后一层,时间复杂度也优于向上建堆,所以向下建堆比向上建堆更优


🌎 堆的经典应用

 堆是一种特殊的数据结构,通过大堆和小堆所带来的特性可以使得堆在许多应用中成为非常有效的数据结构。

🔭 堆排序

 堆排序是一种基于堆数据结构的排序算法,它利用了堆的性质来实现高效的排序。对于一个数组,虽然可以通过堆结构来帮助排序,但是实现一整个堆的数据结构并不容易,并且在建堆时会有额外空间的浪费。

例如:通过堆数据结构进行排序

int main()
{
	int arr[] = { 10,20,25,35,60,36,5 };
	HP heap;
	HeapInit(&heap);
	for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
	{
		HeapPush(&heap, arr[i]);
	}
	int i = 0;
	while (!HeapEmpty(&heap))
	{
		arr[i++] =  HeapTop(&heap);
		HeapPop(&heap);
	}
	HeapDestroy(&heap);
	return 0;
}

  基于这样的一些缺点,所以最好可以实现原地排序。对于任意一个数组是可以看作一个完全二叉树,但是不一定是堆,那么第一个步骤就是建堆。

 类比堆的插入,可以将第一个数组元素看作堆,从第二个元素开始进行向上调整(前提 - 左右子树依旧是堆),这样就可以建堆。

void HeapSort(int* arr, int n)
{
	//第一步:建堆
	int i = 0;
	for (i = 1; i < n; i++)
	{
		AdjustUp(arr, i);
	}
}

⚠注意:

  1. 升序:建大堆。
  2. 降序:建小堆。

📗分析:如果升序建造小堆
【数据结构】深度剖析最优建堆及堆的经典应用 - 堆排列与topk问题,数据结构,数据结构,建堆,堆的应用,堆排序,TOPK
这样的操作代价太大了,为 N*(N*logN),甚至不如直接遍历,丧失了堆的价值。

 当我们想排升序,建造好大堆后,就可以利用堆删除思想来进行排序。
【数据结构】深度剖析最优建堆及堆的经典应用 - 堆排列与topk问题,数据结构,数据结构,建堆,堆的应用,堆排序,TOPK
按照这个逻辑不断交换和调整就可以完成排序,时间代价为 N*logN 。

void HeapSort(int* arr, int n)
{
	//第一步:建堆
	int i = 0;
	for (i = 1; i < n; i++)
	{
		AdjustUp(arr, i);//向上调整
	}
	//第二部:排序
	int end = n - 1;
	while (end > 0)
	{
		Swap(&arr[0], &arr[end]);//交换
		AdjustDown(arr, end, arr[0]);//向下调整
		end--;
	}
}

🔭TOPK问题

 TOPK问题是指从大量数据中获取最大(或最小)的K个数据,例如:有大量的数据,这些数据在内存中存储不下,只能以文件形式存储,现需要找出最大的前k个数。

方式:
1.读取文件前k个数据,在内存数组中建立一个小堆。
2.依次读取剩下的数据,如果大于堆顶元素就进行替换,并向下调整。

当文件的数据全部读取完成后,堆里的数据就是最大的前k个数。

🗼这里以10000个数据为例:

void PrintTok(const char* filename,int k)
{
	//用前k个数据建造一个小堆
	FILE* fout = fopen(filename, "r");
	if (fout == NULL)
	{
		perror("fopen fail");
		exit(-1);
	}
	int* minheap = (int*)malloc(sizeof(int) * k);
	if (minheap == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	int i = 0;
	for (i = 0; i < k; i++)
	{
		fscanf(fout, "%d", &minheap[i]);
	}
	//向下调整建堆
	for (i = (k - 2)/2 ; i >= 0; i--)
	{
		AdjustDown(minheap, k, i);
	}
	//遍历剩下的数据,大的数替代堆顶元素,然后向下调整
	int x = 0;
	while (fscanf(fout, "%d", &x) != EOF)
	{
	    //如果大于堆顶元素就替换
		if (x > minheap[0])
		{
			minheap[0] = x;
			AdjustDown(minheap, k, 0);
		}
	}
	for (i = 0; i < k; i++)
	{
		printf("%d ", minheap[i]);
	}
	printf("\n");
	fclose(fout);
	free(minheap);
}
//造数据
void CreateNDate()
{
	int n = 10000;
	srand((unsigned int)time(NULL));
	const char* file = "Data.txt";
	FILE* fin = fopen(file, "w");
	if (fin == NULL)
	{
		perror("fopen fail");
		exit(-1);
	}
	for (int i = 0; i < n; i++)
	{
		int x = rand() % 1000000;
		fprintf(fin, "%d\n", x);
	}
	fclose(fin);
}

int main()
{
	//CreateNDate();//造数据
	PrintTok("Data.txt", 10);
	return 0;
}

❤️ 结语

 文章到这里就结束了,如果对你有帮助,你的点赞将会是我的最大动力,如果大家有什么问题或者不同的见解,欢迎大家的留言~文章来源地址https://www.toymoban.com/news/detail-714748.html

到了这里,关于【数据结构】深度剖析最优建堆及堆的经典应用 - 堆排列与topk问题的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【数据结构】向上调整建堆和向下调整建堆的天壤之别以及堆排序算法

       💯 博客内容:【数据结构】向上调整建堆和向下调整建堆的天壤之别以及堆排序算法 😀 作  者:陈大大陈 🚀 个人简介:一个正在努力学技术的准前端,专注基础和实战分享 ,欢迎私信! 💖 欢迎大家:这里是CSDN,我总结知识和写笔记的地方,喜欢的话请三连,有

    2024年02月06日
    浏览(35)
  • 数据结构:堆的实现与建堆时间复杂度分析

    目录 前言 一.堆的介绍 1.堆的本质 2.堆的分类 二.堆的实现(以小根堆为例) 1.关于二叉树的两组重要结论: 2.堆的物理存储结构框架(动态数组的简单构建) 3. 堆元素插入接口(以小根堆为例) 堆尾元素向上调整的算法接口: 4.堆元素插入接口测试 5.堆元素插入接口建堆的时间复杂

    2023年04月19日
    浏览(27)
  • 数据结构-哈夫曼树(最优二叉树)

    目录 一、引言 二、哈夫曼树的概念 三、哈夫曼树的构建 1. 构建步骤 2. 构建示例 四、哈夫曼编码 1. 编码规则 2. 编码示例 五、哈夫曼树的应用 1. 数据压缩 2. 文件加密 六、总结 在计算机科学中,数据结构是指计算机中数据组织、管理和存储的方式。数据结构是计算机科学的

    2024年02月07日
    浏览(31)
  • 【数据结构与算法】哈夫曼编码(最优二叉树)实现

    哈夫曼编码 等长编码:占的位置一样 变长编码(不等长编码):经常使用的编码比较短,不常用的比较短 最优:总长度最短 最优的要求:占用空间尽可能短,不占用多余空间,且不能有二义性 这里给出哈夫曼二叉树的实现: HuffmanTree.h: 测试数据(主函数): 运行结果截图

    2024年02月16日
    浏览(31)
  • 【数据结构与算法】哈夫曼编码(最优二叉树实现

    哈夫曼编码 等长编码:占的位置一样 变长编码(不等长编码):经常使用的编码比较短,不常用的比较短 最优:总长度最短 最优的要求:占用空间尽可能短,不占用多余空间,且不能有二义性 这里给出哈夫曼二叉树的实现: HuffmanTree.h: 测试数据(主函数): 运行结果截图

    2024年02月16日
    浏览(31)
  • ffplay播放器剖析(1)----数据结构剖析

    ffplay是FFmpeg源码提供的一个播放器,它是由FFmpeg和SDL的API实现的播放器,对后续播放器的二次开发有着借鉴意义,比如哔哩哔哔哩的ijkplayer. 根据代码可以看出struct MyAVPacketList是一个AVPacket的一个节点,而PacketQueue是用管理这群节点的队列 接下来看一下操作这个队列的一些函数 3

    2024年02月16日
    浏览(31)
  • 【数据结构】详细剖析线性表

    大家好,很高兴又和大家见面啦!!! 经过这段时间的学习与分享,想必大家跟我一样都已经对线性表的相关内容比较熟悉了。为了更好的巩固线性表相关的知识点,下面我们一起将线性表这个章节的内容梳理一遍吧。 线性表的相关概念 线性表时具有 相同数据类型 的 n (

    2024年02月01日
    浏览(88)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包