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

news2024/9/25 23:24:23

文章目录

  • 1 堆排序
  • 2 Top-K问题

1 堆排序

堆排序是一种基于二叉堆(通常使用数组实现)的排序算法。
它的基本思想是利用堆这种数据结构的性质,通过建立一个堆(大堆或小堆),使得堆的根节点是所有节点中的最大值(大堆)或最小值(小堆)。然后,将根节点与堆的最后一个节点交换,使得最大值或最小值进入有序区。接着,对剩余的未排序部分重新调整成堆,重复这个过程,直到整个数组有序。

建堆和堆调整堆中都用到了向下调整,因此掌握了向下调整法,就可以完成堆排序。对该算法不清楚的,可以参考这篇文章,里面进行了详细的介绍:堆详解(C语言实现)
堆排序步骤:

  1. 构建初始堆(建堆): 从最后一个非叶子节点开始,对每个节点进行向下调整(调整成堆的性质,大堆或小堆)。
    排升序:建大堆,原因如下:
    在堆排序中,升序排序要建立大堆的主要原因是为了保证每次选择堆顶元素都是堆中的最大值
    在排升序时,每次选择堆顶元素与堆的最后一个元素交换,由于堆顶是最大值,将其与末尾元素交换后,最大值就被移到了数组的末尾,而在交换后,需要重新调整堆,使剩余部分重新构成大堆,这样,下一次选择堆顶元素时,依然得到的是剩余元素中的最大值。通过这个过程,每次都能选择到当前堆中的最大值,将其移到数组末尾,逐步形成有序部分,从而实现升序排序。
    排降序:建小堆,原因如下:
    在堆排序中,降序排序要建立小堆的主要原因是为了保证每次选择堆顶元素都是堆中的最小值
    在排降序时,每次选择堆顶元素与堆的最后一个元素交换,由于堆顶是最小值,将其与末尾元素交换后,最小值就被移到了数组的末尾,而在交换后,需要重新调整堆,使剩余部分重新构成小堆,这样,下一次选择堆顶元素时,依然得到的是剩余元素中的最小值。通过这个过程,每次都能选择到当前堆中的最小值,将其移到数组末尾,逐步形成有序部分,从而实现降序排序。
  2. 排序: 交换堆的根节点(最大值或最小值)与堆的最后一个节点,并对剩余部分重新调整成堆。
    重复: 重复步骤2,直到整个数组有序。

例如利用堆排序对该数组{ 8, 5, 3, 9, 1}进行排降序,过程如下:

  1. 建小堆
    在这里插入图片描述
  2. 排序
    在这里插入图片描述

代码如下:

void AdjustDown(int* nums, int n, int parent)
{
	// 左孩子的索引
	int child = parent * 2 + 1;
	// 循环直到没有左孩子
	while (child < n)
	{
		// 如果右孩子存在且比左孩子小,选择右孩子
		//若实现大根堆,这里nums[child + 1] < nums[child]的 < 换成 >
		if (child + 1 < n && nums[child + 1] < nums[child])
		{
			++child;
		}
		// 如果孩子比父亲小,交换它们的值
		//若实现大根堆,这里nums[child] < nums[parent]的 < 换成 >
		if (nums[child] < nums[parent])
		{
			// 孩子比父亲大,堆的有序性已经恢复,退出循环
			int tmp = nums[child];
			nums[child] = nums[parent];
			nums[parent] = tmp;
		}
		else
		{
			break;
		}
		// 更新父亲和孩子的索引
		parent = child;
		child = parent * 2 + 1;
	}
}

void HeapSort(int* nums, int n)
{
	//建堆
	//升序,建大堆
	//降序,建小堆
	for (int i = (n - 2) / 2; i >= 0; --i)
	{
		AdjustDown(nums, n, i);//非叶子节点开始向下调整
	}

	int end = n - 1;
	while (end > 0)
	{
		//交换堆的根节点与堆的最后一个节点
		int tmp = nums[end];
		nums[end] = nums[0];
		nums[0] = tmp;
		//并对剩余部分重新调整成堆。
		AdjustDown(nums, end, 0);
		end--;
	}
}

总之,堆排序是一种选择排序,它利用了堆的性质:堆顶的数据,是堆中最大的数据(或者最小的数据)。该算法通过不断选择堆顶元素,将其与堆的最后一个元素交换,然后调整堆,使剩余部分重新构成堆,重复这个过程直到整个数组有序。

2 Top-K问题

Top-K 问题是在一个包含大量数据的集合中,找出前 K 个最大或最小的元素数据的问题。通常数据量都是比较大的。

  1. 关于解决TOP-K问题,我们首先想到的是对这个数据集合拍升序或者降序,然后取前 K 个数据,就能解决这个问题。该方法的缺点是不适用于数据量极大的情况。这是因为,利用排序算法,需要将数据加载到内存中,在内存中进行排序,然而当数据量大到无法一次性加载到内存中时,排序算法的效率就会受到限制。
  2. 因此,就有人提出了使用堆来解决这个问题。该算法的思想是:用数据集的前k个数据,建一个大小为 K 的小顶堆(Top K 最大问题)或大顶堆(Top K 最小问题)。依次遍历剩余n - k个元素,将元素与堆顶比较,若大于(或者小于)堆顶,则替换堆顶,并进行堆调整。这样,最终堆中的元素就是前 K 个最大或最小的元素。

例如:面试题 17.14. 最小K个数
过程如下:

  1. 用数据集的前k个数据,建一个大小为 K 的小根堆。
  2. 依次遍历剩余n - k个元素,将元素与堆顶比较,若大于堆顶,则替换堆顶,并进行堆调整。

代码如下:

 //向下调整算法
 void AdjustDown(int* nums, int n, int parent)
 {
     int child = parent * 2 + 1;
     while (child < n)
     {
         if (child + 1 < n && nums[child + 1] > nums[child])
         {
             ++child;
         }
         if (nums[child] > nums[parent])
         {
             int tmp = nums[child];
             nums[child] = nums[parent];
             nums[parent] = tmp;
         }
         else
         {
             break;
         }
         parent = child;
         child = parent * 2 + 1;
     }
 }
int* smallestK(int* arr, int arrSize, int k, int* returnSize)
{

    int* nums = (int*)malloc(sizeof(int) * k);
    for (int i = 0; i < k; ++i)
    {
        nums[i] = arr[i];
    }
     //前k个数建大堆
    for (int i = (k - 2) / 2; i >=0; --i)
    {
        AdjustDown(nums, k, i);
    }
    //依次遍历剩余n - k个元素
    for (int i = k; i < arrSize; ++i)
    {
        //将元素与堆顶比较,若大于堆顶,则替换堆顶
        if (k > 0 && arr[i] < nums[0])
        {
            nums[0] = arr[i];
            //进行堆调整
            AdjustDown(nums, k, 0);
        }
    }

    *returnSize = k;
    return nums;
}

至此,本片文章就结束了,若本篇内容对您有所帮助,请三连点赞,关注,收藏支持下。
创作不易,白嫖不好,各位的支持和认可,就是我创作的最大动力,我们下篇文章见!
如果本篇博客有任何错误,请批评指教,不胜感激 !!!
在这里插入图片描述

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

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

相关文章

14.Tomcat和HTTP协议-[一篇通]

文章目录 1.HTTP 协议1.1HTTP 是什么1.2理解 "应用层协议"1.3理解 HTTP 协议的工作过程1.4HTTP 协议格式1.4.1抓包工具的使用(Fiddler)1.4.2抓包工具的原理1.4.3抓包结果1.4.4协议格式总结 1.5HTTP 请求 (Request)1.5.1认识 URL1.5.1.1URL 基本格式1.5.1.2关于 URL e…

【算法每日一练]-图论(保姆级教程篇7 最小生成树 ,并查集模板篇)#村村通 #最小生成树

目录 题目&#xff1a;村村通 并查集 题目&#xff1a;最小生成树 kruskal算法 prim算法 先引入问题&#xff1a; 要在n个城市之间铺设光缆&#xff0c;主要目标是要使这 n 个城市的任意两个之间都可以通信&#xff0c;但铺设光缆的费用很高&#xff0c;且各个城市之间铺…

微信小程序nodejs+vue+uniapp视力保养眼镜店连锁预约系统

作为一个视力保养连锁预约的网络系统&#xff0c;数据流量是非常大的&#xff0c;所以系统的设计必须满足使用方便&#xff0c;操作灵活的要求。所以在设计视力保养连锁预约系统应达到以下目标&#xff1a; &#xff08;1&#xff09;界面要美观友好&#xff0c;检索要快捷简易…

【密码学】【安全多方计算】浅析隐私求交PSI

文章目录 隐私求交的定义隐私求交方案介绍1. 基于DH的PSI方案2. 基于OT的PSI方案3.基于OPRF的PSI方案 总结 隐私求交的定义 隐私集合求交使得持有数据参与方通过计算得到集合的交集数据&#xff0c;而不泄露任何交集以外的数据信息。 隐私求交方案介绍 1. 基于DH的PSI方案 …

漏电保护器工作原理

漏电保护器 漏电保护器是低压线路中最常用的保护器之一&#xff0c;简称漏保&#xff0c;又称漏电开关或漏电断路器。漏电保护器除了具有空开的所有保护功能外&#xff0c;还具备漏电保护功能。 需要了解 一根通电导线可以产生磁场&#xff0c;磁场与电流方向遵循右手螺旋关…

[Linux] Linux入门必备的基本指令(不全你打我)

一:ls指令 语法 &#xff1a; ls [选项] [目录或文件] 功能 &#xff1a;对于目录&#xff0c;该命令列出该目录下的所有子目录与文件。对于文件&#xff0c;将列出文件名以及其他信息。 ls不带选项就是显示当前目录下存在的子目录和文件 常用选项: (1). ls -l 功能: 列出…

Vue3-toRaw 和 markRaw 函数

Vue3-toRaw 和 markRaw 函数 toRaw(转换为原始)&#xff1a;将响应式对象转换为普通对象&#xff0c;只适用于 reactive 生成的响应式对象。markRaw(标记为原始)&#xff1a;标记某个对象&#xff0c;让这个对象永远都不具备响应式。一些集成的第三方库&#xff0c;会有大量的…

【解决方案】基于边缘计算技术的安科瑞综合管廊能效管理平台

平台背景 综合管廊一般是建于城市地下用于容纳两类及以上城市工程管线的构筑物及附属设施&#xff0c;将电力、自来水、热力、煤气、电信、网络等市政公用管线根据规划要求集中敷设在同一个构建物内&#xff0c;实施统一设计、施工、管理的市政公用隧道空间&#xff0c;并且还…

短剧小程序开发,短剧视频火热程序

近期&#xff0c;短剧以其独特的魅力在快节奏、忙碌的生活中迅速走红。在匆忙等待食物间隙&#xff0c;或想放松身心的片刻&#xff0c;短句成为人们难得的片刻宁静。 短剧小程序应运而生&#xff0c;在这个小巧的应用中&#xff0c;汇聚了多部丰富多样的正版短剧&#xff0c;为…

ELK---filebeat日志收集工具

filebeat也是日志收集工具&#xff0c;和logstash相同。 filebeat的特点 filebeat是一个轻量级的日志收集工具&#xff0c;所使用的系统资源比logstash部署和启动时使用的资源小的多。 filebeat可以运行在非java环境&#xff0c;他可以代替logstash在非java环境上收集日志 缺…

电脑如何定时关机?

电脑如何定时关机&#xff1f;我承认自己是个相当粗心的人&#xff0c;尤其是在急于离开时经常会忘记关闭电脑&#xff0c;结果就是电量耗尽&#xff0c;导致电脑自动关机。而且&#xff0c;在我使用电脑的时候&#xff0c;经常需要进行软件下载、更新等任务。如果我一直坐等任…

ArkTS-共享元素转场动画

共享元素转场动画 在不同页面间&#xff0c;有使用相同的元素&#xff08;例如同一幅图&#xff09;的场景&#xff0c;可以使用共享元素转场动画衔接。为了突出不同页面间相同元素的关联性&#xff0c;可为它们添加共享元素转场动画。如果相同元素在不同页面间的大小有明显差异…

零信任安全:远程浏览器隔离(RBI)的重要性

引言 在当今数字化时代&#xff0c;网络安全已成为个人和企业关注的焦点。随着网络攻击和恶意软件的不断增加&#xff0c;远程浏览器隔离(RBI)SAAS系统变得至关重要。本文将深入探讨远程浏览器隔离系统的重要性&#xff0c;以及它如何帮助用户保护其网络免受恶意软件和网络攻击…

【傻瓜级JS-DLL-WINCC-PLC交互】1.C#用windows窗体控件创建.net控件

思路 JS-DLL-WINCC-PLC之间进行交互&#xff0c;思路&#xff0c;先用Visual Studio创建一个C#的DLL控件&#xff0c;然后这个控件里面嵌入浏览器组件&#xff0c;实现JS与DLL通信&#xff0c;然后DLL放入到WINCC里面的图形编辑器中&#xff0c;实现DLL与WINCC的通信。然后PLC与…

台式机加独显引发的故事

弄到一块NVIDIA1660显卡&#xff0c;想要加到台式机&#xff0c;枯树逢春&#xff1b;中间引发不少事情&#xff0c;记录下来共勉 1.台式机插入显卡 1&#xff09;拆开主机后部的接口片 2&#xff09;显卡插入显卡巢&#xff0c;很内存条结构类似&#xff08;长短布局&#xff…

zblog插件-zblog采集插件下载

在当今数字化的时代&#xff0c;博客已经成为人们分享思想、经验和知识的重要平台。而对于使用zblog博客系统的用户来说&#xff0c;充实博客内容是提高用户体验和吸引读者的不二法门。然而&#xff0c;手动收集内容对于博主来说既费时又繁琐。在这个背景下&#xff0c;zblog插…

线程为什么比进程的切换效率高

1.进程切换为什么比线程切换效率低呢? 进程都有自己的虚拟地址空间&#xff0c;把虚拟地址转换为物理地址需要查找页表&#xff0c;页表查找是一个很慢的过程&#xff0c;因此通常使用Cache来缓存常用的地址映射&#xff0c;这样可以加速页表查找&#xff0c;这个Cache就是TL…

【Cmake】Cmake基础学习

CMake学习 一、基础学习 1. 利用Cmake进行单个源代码构建可执行文件 (1)基础命令 最基本的 CMake项目是由单个源代码文件构建的可执行文件。对于这样的简单项目,只需要一个包含三个命令的 CMakeLists.txt 文件。 注意: 虽然 CMake 支持大写、小写和混合大小写命令,但是…

【Unity3D】MAX聚合广告SDK——Pangle广告接入(成了!成了!)

Pangle, App Monetization Simplified 注册 登录 创建应用 创建广告单元 将其应用ID和广告ID关联到MAX广告。 下载Pangle Unity Plugin包&#xff0c;新建一个空工程&#xff08;很重要&#xff09; Unity版本2019.4.0f1 gradle plugin 4.2.0 gradle版本6.7.1 build_tools 34.…

【Java】7. 类型转换和类型判断

7. 类型转换 7.1 基本类型转换 顺箭头&#xff1a;隐式转换&#xff08;自动&#xff09; 逆箭头&#xff1a;强制转换&#xff08;可能造成精度丢失&#xff09; byte a 10; int b a; int c 1000; byte d (byte) c; System.out.println(d); // -24 7.2 包装类型与基…