数据结构:堆的应用(堆排序和topk问题)

news2024/11/24 17:44:49

在这里插入图片描述

个人主页 : 个人主页
个人专栏 : 《数据结构》 《C语言》

文章目录

  • 堆排序
    • 建堆
    • 堆的删除思想排序
    • 代码实现
  • top k 问题
    • 思路
    • 代码实现
  • 总结


堆排序

堆排序即是 先将数据建堆,再利用堆删除的思想来排序。

  1. 将待排序数组建堆
  2. 将堆顶数据与数组尾部数据交换
  3. 调整新的堆顶数据,使其保证堆的结构不变

重复2,3步直到堆中没有数据结束。

建堆

  • 降序 建小堆 (父节点 小于等于 子节点)
  • 升序 建大堆 (父节点 大于等于 子节点)

建堆有两种思路,向上建堆 和 向下建堆。其中向下建堆优于向上建堆。

向下建堆:从最后一个子节点的父节点开始向前遍历待排序数组,不断向下调整。
如下: 对数组 {16, 72, 31, 94, 53, 23}建小堆
在这里插入图片描述
为什么不能从数组首元素开始呢? 因为向下调整的前提是 根节点的左子树 与 右子树都是大堆或小堆才可以使用。而空树 和 只有一个节点的树即可以是大堆或小堆。

堆的删除思想排序

  • 将堆顶数据 与 未排序数组尾部数据 交换
  • 向下调整新的堆顶数据,保证堆的结构不变
  • 将新未排序数组尾部数据 与 新堆顶数据交换

重复上述步骤,即可完成排序。
也可以解释为什么升序建大堆, 降序建小堆。小堆的堆顶数据永远是堆中数据最小的,将堆顶数据与未排序数组尾部交换,重复上述步骤。最小的数据就是数组最后一个元素,第二小的数据就是数字倒数第二个元素… 如此完成了降序。

如下

在这里插入图片描述

代码实现

//向下调整 小堆,假设该节点是 i, 右孩子节点是 2 * i + 1,左孩子节点是 2 * i + 2
void AdjustDown(HPDataType* data, int parent, int size)
{
	int child = parent * 2 + 1;

	while (parent < size)
	{
		//防止越界                    找左右孩子中最小的
		if (child + 1 < size && data[child] > data[child + 1])
		{
			child++;
		}

		if (child < size && data[parent] > data[child])
		{
			swap(&data[parent], &data[child]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}


// 对数组进行堆排序
//先构建堆    升序:大堆     降序:小堆
//如降序,先建小堆,再将堆顶数据放入数组尾部,从新选择堆顶数据
void HeapSort(int* a, int n)
{
	建堆
	向上建堆   类似于插入数据
	//for (int i = 0; i < n; i++)
	//{
	//	AdjustUp(a, i);
	//}

	//向下建堆   向下调整的前提:该节点的左右子树要都是大堆或小堆
	//倒着从第一个非叶子结点开始向下建堆
	//             n 是数据个数 n-1 是数组最后一个元素   (子节点 - 1) / 2 == 父节点
	for (int i = (n - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(a, i, n);
	}


	//将堆顶数据交换数组尾部数据,再选新的堆顶,再交换新的数组尾
	int end = n - 1;
	while (end > 0)
	{
		swap(&a[0], &a[end]);
		AdjustDown(a, 0, end);
		end--;
	}
}

int main()
{
	int arr[] = { 16, 72, 31, 23, 94, 53 };
	int size = sizeof(arr) / sizeof(arr[0]);

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

top k 问题

top k问题就是从N个数中选出前K个数 (N远大于K)
如下:我们随机创建 10000个小于1000000的数,从中找到5个最大的数

思路

我们可以先以前5个数建小堆,再遍历9995个数,如果该数大于堆顶的数,将该数与堆顶的数替换,再向下调整保证小堆结构,继续遍历剩下的数,直到遍历完9995个数。那么堆中的5个数就是10000中最大的5个数。

代码实现

如何检查代码的正确性?
我们可以先跑一遍造数据的代码,再在其创建的文件中随机改写5个数,使其大于1000000。然后我们就可以屏蔽造数据的函数,来运行PrintTopK函数。

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

void CreateNDate()
{
	// 造数据
	int n = 10000;
	srand((unsigned)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() % 1000000;
		fprintf(fin, "%d\n", x);
	}

	fclose(fin);
}




//从N个数中选处最大的K个数
//用前K个数建小堆(向下调整 or 向上调整),遍历N - K 个数,  (如果是大堆,那么有可能堆顶数据在一开始就是 N 个数中最大的)
//如果该数大于堆顶数据,堆顶数据 与 该数 交换在向下调整。
//遍历完 N - K 个数,那么堆中数据就是 N 个数中最大的 K 个数

void swap(int* a, int* b)
{
	int tmp = *a;
	*a = *b;
	*b = tmp;
}

//小堆  父节点小于等于子节点
void AdjustDown(int* data, int parent, int size)
{
	int child = parent * 2 + 1;

	while (parent < size)
	{

		if (child + 1 < size && data[child] > data[child + 1])
		{
			child++;
		}

		if (child < size && data[parent] > data[child])
		{
			swap(&data[child], &data[parent]);

			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

void PrintTopK(int k)
{
	const char* file = "data.txt";
	FILE* fin = fopen(file, "r");

	//读取前K个数据
	int* ans = (int*)malloc(sizeof(int) * (k + 1));
	if (ans == NULL)
	{
		perror("malloc:");
		exit(-1);
	}

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

	//建堆
	for (int i = (k - 1) / 2; i >= 0; i--)
	{
		AdjustDown(ans, i, k);
	}

	while (!feof(fin))
	{	
		//读取数据
		int val = 0;
		fscanf(fin, "%d", &val);

		if (val > ans[0])
		{
			swap(&val, &ans[0]);
			AdjustDown(ans, 0, k);
		}
	}

	
	//打印数据
	for (int i = 0; i < k; i++)
	{
		printf("%d ", ans[i]);
	}
	printf("\n");
}


int main()
{
	CreateNDate();

	int k = 0;
	scanf("%d", &k);
	PrintTopK(k);
	return 0;
}

总结

以上就是我对于堆的应用的理解!!!
在这里插入图片描述

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

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

相关文章

【python】爬取豆瓣电影Top250(附源码)

前言 在网络爬虫的开发过程中&#xff0c;经常会遇到需要处理一些反爬机制的情况。其中之一就是网站对于频繁访问的限制&#xff0c;即IP封禁。为了绕过这种限制&#xff0c;我们可以使用代理IP来动态改变请求的来源IP地址。在本篇博客中&#xff0c;将介绍如何使用代理IP的技术…

RHEL 7配置HAProxy实现Web负载均衡

一、测试环境HAProxy&#xff1a; 主机名&#xff1a;RH7-HAProxy IP地址&#xff1a;192.168.10.20 操作系统&#xff1a;Red Hat Enterprise Linux Server release 7.2 (Maipo)最小化安装 防火墙与SELinux&#xff1a;关闭 安装的服务&#xff1a;HAProxy-1.5.14 WEB01: 主…

[管理与领导-12]:IT基层管理者 - 绩效面谈 - 如何面谈,遇到问题员工怎么办?

目录 前言&#xff1a; 第1章 问题现象 第一&#xff1a;面谈没效果 第二&#xff1a;问题没解决 第三&#xff1a;双方都不满 第2章 背后原因 1.1 面谈变成了训话&#xff1a;引导变成训话 1.2 面谈变成了扯皮&#xff1a;立足未来变成纠缠过去 1.3 面谈变成了双方较…

vite4+vue3:从0到1搭建vben后台管理系统(五)-封装属于自己的form组件

日常业务开发中的,避免不了的要接触到表单开发,那么我们有必要了解表单的深入使用方法,及封装出适合自己业务的自定义表单组件,效果如下: 不是用ele和antdvue框架,实现自己的表单组件!!! ValidateForm组件封装 <template><div class="validate-form…

c++学习(多线程)[33]

thread 本质封装操作系统的库 事实证明&#xff0c;两个线程在cpu中交错运行 thread传参为模板参数&#xff0c;应用折叠&#xff0c;都会变成左值&#xff0c;所以count还是0 sleep_until 在C中&#xff0c;没有直接的sleep_until函数&#xff0c;但可以使用std::th…

c++ 有元

友元分为两部分内容 友元函数友元类 友元函数 问题&#xff1a;当我们尝试去重载operator<<&#xff0c;然后发现没办法将operator<<重载成成员函数。因为cout的输出流对象和隐含的this指针在抢占第一个参数的位置。this指针默认是第一个参数也就是左操作 数了。…

dbm与mw转换

功率值10^(dBm值/10)&#xff0c;单位mW。 对于-5dBm&#xff0c;其功率值为0.3162 mW。 dBm 10 * lg(mW&#xff09;

C++ STL vector 模拟实现

✅<1>主页&#xff1a;我的代码爱吃辣 &#x1f4c3;<2>知识讲解&#xff1a;C之STL &#x1f525;<3>创作者&#xff1a;我的代码爱吃辣 ☂️<4>开发环境&#xff1a;Visual Studio 2022 &#x1f4ac;<5>前言&#xff1a;上次我们已经数字会用…

创建多图层叠加效果的背景与人物图像

引言&#xff1a; 在现代应用程序开发中&#xff0c;图形资源的使用是非常常见的&#xff0c;特别是在用户界面设计中。通过使用TImageList和TGlyph组件的组合&#xff0c;我们可以实现令人印象深刻的多图层叠加效果。本文将介绍如何使用这两个组件来创建背景和人物的多图层叠加…

doubletrouble靶机通关详解

信息收集 漏洞发现 扫目录 发现secret路径 里面有个图 qdPM9.1 网上找找exp 反弹shell http://192.168.0.107//uploads/users/632300-backdoor.php?cmdecho "<?php eval(\$_POST[1]);?>" > 1.php 蚁剑连上去传php-reverse-shell.php 提权 优化shell…

企业微信爆出漏洞,公司员工被迫摸鱼

「作者主页」&#xff1a;士别三日wyx 「作者简介」&#xff1a;CSDN top100、阿里云博客专家、华为云享专家、网络安全领域优质创作者 「推荐专栏」&#xff1a;对网络安全感兴趣的小伙伴可以关注专栏《网络安全入门到精通》 昨晚加班好好的&#xff0c;突然接到公司通知&…

不可能,绝对不可能

前言 有 2 个月未更了&#xff0c;读者朋友微信留言&#xff1a;“亮哥&#xff0c;最近是不是颓了&#xff1f;好久未更了” 我随即回复&#xff1a; 没想到这时兄弟发来了&#xff1a; 好吧&#xff0c;给大家汇报下近况&#xff0c;不枉大家一直激励我前行。 项目管理 前段时…

MySQL分表实现上百万上千万记录分布存储的批量查询设计模式

我们知道可以将一个海量记录的 MySQL 大表根据主键、时间字段&#xff0c;条件字段等分成若干个表甚至保存在若干服务器中。唯一的问题就是跨服务器批量查询麻烦&#xff0c;只能通过应用程序来解决。谈谈在Java中的解决思路。其他语言原理类似。这里说的分表不是 MySQL 5.1 的…

STM32入门学习之定时器PWM输出

1.脉冲宽度调制PWM(Pulse Width Modulation)是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术。PWM可以理解为高低电平的占空比&#xff0c;即输出高电平时间与低电平时间的比值。PWM的应用是否广泛&#xff0c;比如在步进电机的控制中&#xff0c;可以通过P…

WebRTC本地视频通话使用ossrs服务搭建

iOS开发-ossrs服务WebRTC本地视频通话服务搭建 之前开发中使用到了ossrs&#xff0c;这里记录一下ossrs支持的WebRTC本地服务搭建。 一、ossrs是什么&#xff1f; ossrs是什么呢&#xff1f; SRS(Simple Realtime Server)是一个简单高效的实时视频服务器&#xff0c;支持RTM…

【佳佳怪文献分享】通过引导学会行走: 动态环境中的感知四足运动

标题&#xff1a;Learning to Walk by Steering: Perceptive Quadrupedal Locomotion in Dynamic Environments 作者&#xff1a;Mingyo Seo , Ryan Gupta , Yifeng Zhu , Alexy Skoutnev , Luis Sentis , and Yuke Zhu 来源&#xff1a;2023 IEEE International Conference …

Titanic--细节记录二

merge、join以及concat的方法的不同以及相同 相同之处&#xff1a;都用于合并数据。 不同之处&#xff1a; merge主要是基于列的合并。join主要是基于索引&#xff08;行标签&#xff09;的合并。concat可以沿任意轴合并&#xff0c;更灵活。 import pandas as pddf1 pd.Da…

Linux 查看内存使用情况的几种方法

在运行 Linux 系统的过程中为了让电脑或者服务器以最佳水平运行&#xff0c;常常需要监控内存统计信息。 那么今天我们就来看看有哪些方法可以访问所有相关信息并帮助管理员监控内存统计信息。 查看或者获取 Linux 中的内存使用情况既可以通过命令的方式&#xff0c;也可以通…

OptaPlanner笔记6 N皇后

N 个皇后 问题描述 将n个皇后放在n大小的棋盘上&#xff0c;没有两个皇后可以互相攻击。 最常见的 n 个皇后谜题是八个皇后谜题&#xff0c;n 8&#xff1a; 约束&#xff1a; 使用 n 列和 n 行的棋盘。在棋盘上放置n个皇后。没有两个女王可以互相攻击。女王可以攻击同一水…

Python语言基础---选择判断循环结构详解

文章目录 &#x1f340;引言&#x1f340;if语句&#x1f340;if-else语句&#x1f340;if-elif-else语句&#x1f340;for循环&#x1f340;while循环 &#x1f340;引言 在Python编程语言中&#xff0c;选择判断和循环是两个非常重要的概念。它们可以让我们根据条件执行不同的…