【初阶数据结构】——堆排序和TopK问题

news2025/1/11 19:51:42

 =========================================================================

个人主页

代码仓库

C语言专栏

初阶数据结构专栏

Linux专栏

 =========================================================================

接上篇二叉树和堆的引入

========================================================================= 

目录

前言

建堆

插入数据向上调整算法建堆

移动数据向上调整算法建堆

无序数组从H-1层向上移动的向下调整算法建堆

堆排序

TOP-K问题


前言

上篇文章详细讲解了堆,最后在执行完整代码后我们发现在删除堆中的数据时可以实现排序,当然这不是偶然,一切都是有迹可循的,今天就来讲解下用堆来实现排序,以及使用堆排序解决TopK问题。

建堆

插入数据向上调整算法建堆

上篇文章中我们就实现了这个步骤,在主函数中创建了个数组然后将数组中的每个数据使用插入函数和向上调整算法函数依次插入动态开辟的空间中,每插入一个数据作为孩子和父亲相比较,根据大小交换位置,最终实现大/小堆。

插入函数

void HPPush(HP* php, HPDatatype x)
{
	assert(php);
	
	if (php->size == php->capacity)
	{
		int newcapacity = php->capacity == 0 ? 4 : php->capacity * 2;
		HPDatatype* tmp = (HPDatatype*)realloc(php->a, sizeof(HPDatatype)*newcapacity);
		if (tmp == NULL)
		{
			perror("realloc failed");
			exit(-1);
		}
		php->a = tmp;
		php->capacity = newcapacity;
	}
	php->a[php->size] = x;
	php->size++;
	HPadjustUp(php->a, php->size-1);
}

进入函数首先判断空间大小是否足够 ,不够的话使用realloc库函数开辟空间,开辟不成功直接退出,开辟成功的话赋值和修改size和容量。 

向上调整函数和交换函数 

void HPadjustUp(HPDatatype* a, int child)
{
	//找到父亲
	int parent = (child - 1) / 2;
	//根为0  当和根交换后child为0
	while (child > 0)
	{
		//当child小时和父亲交换 建成小堆
		//当child大时和父亲交换 建成大堆
		if (a[parent] > a[child])
		{
			swap(&a[parent], &a[child]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

 进入向上调整函数根据我们上篇内容提供父亲和孩子之间下标的关系,依次向上调整根据我们的需要和父亲孩子的大小关系,用交换函数实现大/小堆。

​
void swap(HPDatatype* x, HPDatatype* y)
{
	HPDatatype tmp = *x;
	*x = *y;
	*y = tmp;
}

​

为防止局部变量出交换函数作用域被销毁,这里我们使用指针交换。

这种方式的缺点

1.需要动态开辟空间,造成空间浪费。

2.需要完整的堆实现的代码,比较麻烦不是很推荐。

我们可以使用下面的方法对上面的函数进行优化。


移动数据向上调整算法建堆

根据我们这个方法的名字就可以判断我们这个方法是不需要动态开辟额外的空间,只需要使用数组下标通过向上调整算法函数来实现。

实现代码

	for (int i = 1; i < n; i++)
	{
		HPadjustUp(a, i);
	}

 这里我们将数组中的第一个数据作为一个堆通过下标的移动让后面的数字和前面的数字比较,也就相相当于前面的数字作为父亲后面的数字作为孩子,父亲和孩子使用向上调整函数进行调整实现堆。

像这样经过多次的移动就形成我们的堆。  


无序数组从H-1层向上移动的向下调整算法建堆

上篇文章我们介绍了向下移动的调整算法,但是这个算法有个前提就是除根外的左右子树都要是堆,但是我们这里给定一个无序数组先让这个数组模拟成堆,除根外左右子树有可能都不是堆,就无法实现向下调整算法,这样我们从倒数第一个非子叶结点开始向下调整也就是最后一个结点的父亲开始向下调整,我们直到数组在空间中是连续的,那我们从这个结点开始没向下调整依次,这个结点向前移动依次这样就将一个大堆分成各个小堆,完成向下调整了。

实现代码

for (int i = (n - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(a, n, i);
	}

像这样经过多次的向下调整,最终就可以实现堆。 


堆排序

在实现堆排序之前我们先思考下升序和降序需要建什么堆?

我们这里直接给出答案:

升序:建大堆

降序:建小堆

问什么会是这样呢?

在上篇文章中堆的删除中,我们已经隐含的告诉大家了,如果我们要删除堆中的数据时直接向前移动数据会造成不是原有的大堆或者小堆,因此我们将根结点的数据和最后一个数据交换,两个子树依然是堆然后进行向下调整,size向前移动。我们不做删除这一步是不是就是排序!

实现代码

int end = n - 1;
	while (end > 0)
	{
		swap(&a[end], &a[0]);
		AdjustDown(a, end, 0);
		end--;
	}

TOP-K问题

TOP-K问题:即求数据结合中前K个最大的元素或者最小的元素,一般情况下数据量都比较大。
比如:专业前10名、世界500强、富豪榜、游戏中前100的活跃玩家等。
对于Top-K问题,能想到的最简单直接的方式就是排序,但是:如果数据量非常大,排序就不太可取了(可能数据都不能一下子全部加载到内存中)。最佳的方式就是用堆来解决,基本思路如下:
1. 用数据集合中前K个元素来建堆
前k个最大的元素,则建小堆
前k个最小的元素,则建大堆
2. 用剩余的N-K个元素依次与堆顶元素来比较,不满足则替换堆顶元素将剩余N-K个元素依次与堆顶元素比完之后,堆中剩余的K个元素就是所求的前K个最小或者最大的元素。

void PrintTopK(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");

	free(minheap);
	fclose(fout);
}

// fprintf  fscanf

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);
}

int main()
{
	//CreateNDate();
	PrintTopK("data.txt", 5);

	return 0;
}

文件操作知识忘了的话自己回顾下。

今天的内容到这里就结束了,感谢大家的观看!可以在评论区多多交流和探讨,指出我的错误!

下篇文章将讲解完全二叉树的实现!请大家敬请期待!!!

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

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

相关文章

腐蚀与膨胀,图像处理中的结构元与腐蚀膨胀问题(数字图像处理大题复习 P9)

文章目录 腐蚀膨胀 腐蚀 我们要用结构元 B 来 腐蚀 图像 A 如果结构元 B 放到图像的某个地方&#xff0c;正好能对上&#xff0c;那么就把中心放到结果图像上 这里显然红色框是对不上的&#xff0c;而黄色是对得上的 对不上的&#xff0c;全部补上0&#xff0c;这就是结果了…

HTTP 响应头Cache-Control

每个资源都可以通过Http头Cache-Control来定义自己的缓存策略&#xff0c;Cache-Control控制谁在什么条件下可以缓存响应以及可以缓存多久。 最快的请求是不必与服务器进行通信的请求&#xff1a;通过响应的本地副本&#xff0c;我们可以避免所有的网络延迟以及数据传输的数据…

新版考勤管理系统正式发布

O2OA(翱途)开发平台 V8.1版本&#xff0c;因老的考勤管理系统已经无法满足用户需求&#xff0c;并且在架构和业务结构上都不再符合现在大多数考勤功能的需求。我们对考勤管理重新进行了开发&#xff0c;全新的版本更好用&#xff0c;更直观。 考勤管理对员工的工作出勤情况进行…

Multisim—用示波器观察电流波形

使用仪表里面的current probe&#xff1a;它显示实时电流&#xff0c;但无法将电流波形在示波器中显示。 仿真Simulate >仪器Instruments > 电流探针 current clamp&#xff1a;也就是电流钳&#xff0c;它能够将流经的电流转成电压并输入到示波器中进行显示。 双击图纸上…

手机上比较好用的笔记软件使用哪一款?

手机已经成为我们日常生活不可或缺的一部分&#xff0c;它们伴随着我们的方方面面。在这部小小的设备中&#xff0c;我们可以完成许多任务&#xff0c;其中之一就是记录笔记。手机上的笔记软件如今多种多样&#xff0c;但在选择时&#xff0c;敬业签可能是你不容错过的选择。 …

软件需求文档、设计文档、开发文档、运维文档大全

在软件开发过程中&#xff0c;文档扮演着至关重要的角色。它不仅记录了项目的需求、设计和开发过程&#xff0c;还为项目的维护和管理提供了便利。本文将详细介绍软件开发文档的重要性和作用&#xff0c;以及需求分析、软件设计、开发过程、运维管理和项目管理等方面的文档要求…

YOLOv8快速复现 官网版本 ultralytics

YOLOV8环境安装教程.&#xff1a;https://www.bilibili.com/video/BV1dG4y1c7dH/ YOLOV8保姆级教学视频:https://www.bilibili.com/video/BV1qd4y1L7aX/ b站视频&#xff1a;https://www.bilibili.com/video/BV12p4y1c7UY/ 1 平台搭建YOLOv8 平台&#xff1a;https://www.a…

链表反转-LeetCode206

题目&#xff1a; 给你单链表的头结点head&#xff0c;请反转链表&#xff0c;并返回反转后的链表。 示例&#xff1a; 输入&#xff1a;head [1&#xff0c;2&#xff0c;3&#xff0c;4&#xff0c;5] 输出&#xff1a;[5&#xff0c;4&#xff0c;3&#xff0c;2&#xff0…

MongoDB(二)基础操作 创建、删除等

mongodb有一个特点&#xff0c;如果某个库&#xff0c;库下面没数据&#xff08;mongodb成集合&#xff09;&#xff0c;该库等于不存在的 mongodb只要创建一个库&#xff0c;在库下写入数据&#xff0c;该库才会生成 mongoshe [-hhost -pxxx] 创建数据库 use 数据库名 # 如果…

四种自动化测试模型实例及优缺点

一&#xff0c;线性测试 1.概念&#xff1a; 通过录制或编写对应应用程序的操作步骤产生的线性脚本。单纯的来模拟用户完整的操作场景。 &#xff08;操作&#xff0c;重复操作&#xff0c;数据&#xff09;都混合在一起。 2.优点&#xff1a; 每个脚本相对独立&#xff0…

【element-ui】form表单动态修改rules校验项

在项目开发过程中&#xff0c;该页面有暂存和提交两个按钮&#xff0c;其中暂存和提交必填项校验不一样&#xff0c;此时需要动态增减必填项校验 &#xff0c;解决方法如下&#xff1a; 增加rules校验项 this.$set(this.formRules,name,[{required:true,message:请输入名称,t…

Git 介绍、分布式版本管理软件介绍

文章目录 一. 分布式文件版本管理系统二、Git 介绍2.1. Git 的最基本使用2.2. 工作中使用版本管理工具的经验2.3. Git 的存储方式简介 一. 分布式文件版本管理系统 在分布式文件版本管理系统到来之前&#xff0c;市面上的文件版本管理软件都是集中式的&#xff08;svn 就是典型…

演讲实录:DataFun 垂直开发者社区基于指标平台自主洞察北极星指标

在7月14日举办的 Kyligence 用户大会的数智新应用论坛上&#xff0c;DataFun COO 杜颖女士为大家带来了《垂直开发者社区基于指标平台自主洞察北极星指标》的主题演讲。接下来&#xff0c;我们一起看看 DataFun 如何在没有专门的 IT 团队的情况下&#xff0c;实现对北极星指标的…

开源框架中的责任链模式实践

作者&#xff1a;vivo 互联网服务器团队-Wang Zhi 责任链模式作为常用的设计模式而被大家熟知和使用。本文介绍责任链的常见实现方式&#xff0c;并结合开源框架如Dubbo、Sentinel等进行延伸探讨。 一、责任链介绍 在GoF 的《设计模式》一书中对责任链模定义的&#xff1a;将…

基于TensorFlow+CNN+协同过滤算法的智能电影推荐系统——深度学习算法应用(含微信小程序、ipynb工程源码)+MovieLens数据集(七)

目录 前言总体设计系统整体结构图系统流程图 运行环境模块实现1. 模型训练1&#xff09;数据集分析2&#xff09;数据预处理3&#xff09;模型创建4&#xff09;模型训练5&#xff09;获取特征矩阵 2. 后端Django3. 前端微信小程序1&#xff09;小程序全局配置文件2&#xff09…

时序预测 | MATLAB实现POA-CNN-LSTM鹈鹕算法优化卷积长短期记忆神经网络时间序列预测

时序预测 | MATLAB实现POA-CNN-LSTM鹈鹕算法优化卷积长短期记忆神经网络时间序列预测 目录 时序预测 | MATLAB实现POA-CNN-LSTM鹈鹕算法优化卷积长短期记忆神经网络时间序列预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 MATLAB实现POA-CNN-LSTM鹈鹕算法优化卷积长短…

C语言每日一题(1):实现库函数strlen()

文章主题&#xff1a;库函数strlen()的实现所属专栏&#xff1a;C语言每日一题作者简介&#xff1a;每天不定时更新C语言的小白一枚&#xff0c;记录分享自己每日的所思所想。个人主页&#xff1a;[₽]的个人主页 目录 前言 函数介绍 编程起因 设计思路 1.整体逻辑 2.参…

Postman 的使用教程(详细)

Postman 使用教程 1. 是什么 Postman 是一个接口测试工具软件&#xff0c;可以帮助开发人员管理测试接口。 官网&#xff1a;https://www.getpostman.com/ 2. 安装 建议通过官网下载安装&#xff0c;不要去那些乱七八糟的下载平台&#xff0c;或者留言获取 官网下载地址&am…

【unity】关于技能释放shader.CreateGPUProgram造成卡顿,优化和定位方法。

关于优化方法&#xff0c;UWA这边有介绍 Unity移动端游戏性能优化简谱之 CPU耗时调优|单帧|动画|调用|unity|实例化_网易订阅 对此&#xff0c;我们可以将Shader通过ShaderVariantCollection收集要用到的变体并进行AssetBundle打包。在将该ShaderVariantCollection资源加载进内…

30.CSS文本悬停过渡效果

效果 源码 index.html <!doctype html> <html> <head><meta charset="utf-8"><title>CSS Focus Text Hover Effects</title><link rel="stylesheet" href="style.css"> </head> <body>…