堆排序在topK场景题中的应用及原理

news2025/7/7 23:53:04

参考以下文章:

  • 堆排序(大顶堆、小顶堆)----C语言
  • 006 查找第k大的数——堆结构的初应用
  • 寻找最大的K个数,Top K问题的堆实现
  • 海量数据查找最大的前k个数(小顶堆)

零、先简单说下处理topK问题的答案:

一般我们说 topK 问题,通常使用大顶堆小顶堆来实现:

  • 1.从N个数中寻找最大的 K 个数 / 在未排序的数组中找到第k个最大的元素
    • 小顶堆(优先推荐:【空间复杂度 O(K)】,因为只需要维护数量为K个节点的堆结构;【时间复杂度 N*O(logK)】
    • 大顶堆(不推荐):【空间复杂度 O(N)】,因为需要将N个数全部都填充进 [大顶堆] 中,然后再弹出K次;【创建堆的时间复杂度:O(N);调整堆的时间复杂度:KO(logN),因为 k < n,故总的渐进时间复杂为 O(N + KlogN) = O(NlogN)】
  • 2.从N个数中寻找最小的 K 个数 / 在未排序的数组中找到第k个最小的元素
    • 大顶堆(优先推荐:【空间复杂度 O(K)】,因为只需要维护数量为K个节点的堆结构;【时间复杂度 N*O(logK)】
    • 小顶堆:【空间复杂度 O(N)】,因为需要将N个数全部都填充进 [小顶堆] 中,然后再弹出K次;【创建堆的时间复杂度 O(N);调整堆的时间复杂度 KO(logN),因为 k < n,故总的渐进时间复杂为 O(N + KlogN) = O(NlogN)】
  • 总的来说,无论是【寻找最大的 K 个数 / 在未排序的数组中找到第k个最大的元素】,还是【从N个数中寻找最小的 K 个数 / 在未排序的数组中找到第k个最小的元素】,它们都分别可以使用 [大顶堆/小顶堆] 来处理,只是不同方式,其时间复杂度和空间复杂度也有所不同。
    • 推荐【从N个数中寻找最大的 K 个数 / 在未排序的数组中找到第k个最大的元素】使用小顶堆!
    • 推荐【从N个数中寻找最小的 K 个数 / 在未排序的数组中找到第k个最小的元素】使用大顶堆!

举例:LeetCode
215.数组中的第k个最大的元素,推荐其题解:『
TopK问题 』快速排序、堆排序详解

这一题如果我们采用[堆]的思想,要怎么考虑呢?

求第k大,就要用小顶堆,每一次更新后,顶点时刻都是整个树中数值最小的节点,它的孩子都比他大,而我们求第k大,就构建一个只有 k
个节点的堆,最后返回堆顶。是不是很妙?!

我把堆的排序规则简要归纳为 [对比] → [移除] → [调整] 三步。看一个示意图:
在这里插入图片描述
由于节点个数的限制,一个堆里只能存放k个节点(空间复杂度:O(K)),那么在初始堆填满后,后续新来的每一个值都要先跟堆顶比较值的大小。因为小顶堆的堆顶始终保存整个树结构中最小的那个值。刷新,然后调整,继续保持根节点最小的队形。

一、什么是堆?

堆是一种非线性结构,(本篇随笔主要分析堆的数组实现)可以把堆看作一个数组,也可以被看作一个完全二叉树,通俗来讲堆其实就是利用 完全二叉树 的结构来维护的一维数组。

按照堆的特点可以把堆分为大顶堆和小顶堆:

  • 大顶堆:每个结点的值都大于或等于其左右孩子结点的值

  • 小顶堆:每个结点的值都小于或等于其左右孩子结点的值

(堆的这种特性非常的有用,堆常常被当做优先队列使用,因为可以快速的访问到“最重要”的元素)

二、堆的特点(数组实现)

大顶堆和小顶堆
我们对堆中的结点按层进行编号,将这种逻辑结构映射到数组中就是下面这个样子
堆的数组实现
我们用简单的公式来描述一下堆的定义就是:(读者可以对照上图的数组来理解下面两个公式)

  • 大顶堆:arr[i] >= arr[2i+1] && arr[i] >= arr[2i+2]
  • 小顶堆:arr[i] <= arr[2i+1] && arr[i] <= arr[2i+2]

三、堆和普通树的区别

  • 内存占用:

普通树占用的内存空间比它们存储的数据要多。你必须为节点对象以及左/右子节点指针分配额外的内存。堆仅仅使用数组,且不使用指针
(可以使用普通树来模拟堆,但空间浪费比较大,不太建议这么做)

  • 平衡:
    二叉搜索树必须是“平衡”的情况下,其大部分操作的复杂度才能达到O(nlog2n)。你可以按任意顺序位置插入/删除数据,或者使用 AVL 树或者红黑树。但是在堆中实际上不需要整棵树都是有序的。我们只需要满足堆属性即可,所以在堆中平衡不是问题。因为堆中数据的组织方式可以保证O(nlog2n) 的性能

  • 搜索:
    在二叉树中搜索会很快,但是在堆中搜索会很慢。在堆中搜索不是第一优先级,因为使用堆的目的是将最大(或者最小)的节点放在根节点,从而快速的进行相关插入、删除操作

四、堆排序的过程

先了解下堆排序的基本思想:

将待排序序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点。将其与末尾元素进行交换,此时末尾就为最大值。然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。

如此反复执行,便能得到一个有序序列了,建立最大堆时是从最后一个非叶子节点开始从下往上调整的(这句话可能不好太理解),下面会举一个例子来理解堆排序的基本思想

给一个无序序列如下:
int a[6] = {7, 3, 8, 5, 1, 2};
现在可以根据数组将完全二叉树还原出来
在这里插入图片描述
好了,现在我们要做的事情就是要把7,3,8,5,1,2变成一个有序的序列,如果想要升序就是1,2,3,5,7,8 如果想要降序就是8,7,5,3,2,1 ,这两种就是我们要的最终结果,然后我们就可以根据我们想要的结果来选择适合类型的堆来进行排序。

  • 升序 → 使用大顶堆
  • 降序 → 使用小顶堆

五、堆排序的原理

上面提到过大顶堆的特点:每个结点的值都大于或等于其左右孩子结点的值,我们把大顶堆构建完毕后根节点的值一定是最大的,然后把根节点的和最后一个元素(也可以说最后一个节点)交换位置,那么末尾元素此时就是最大元素了(理解这点很重要)

知道了堆排序的原理后,再理清一下步骤(假设我们想要升序的排列)

  1. 构建堆:先用n个元素的无序序列,构建成大顶堆
  2. 调整堆 1:将根节点与最后一个元素交换位置,(将最大元素"沉"到数组末端)
  3. 调整堆 2:交换过后可能不再满足大顶堆的条件,所以需要将剩下的 n-1 个元素重新构建成大顶堆
  4. 重复第二步、第三步直到整个数组排序完成

六、图解交换过程(得到升序序列,使用大顶堆来调整)

这里以 int a[6] = {7, 3, 8, 5, 1, 2}为例子

先要找到最后一个非叶子节点,数组的长度为6,那么最后一个非叶子节点就是:长度/2-1,也就是6/2-1=2,然后下一步就是比较该节点值和它的子树值,如果该节点小于其左\右子树的值就交换(意思就是将最大的值放到该节点)

8只有一个左子树,左子树的值为2,8>2不需要调整
在这里插入图片描述
下一步,继续找到下一个非叶子节点(其实就是当前坐标-1就行了),该节点的值为3小于其左子树的值,交换值,交换后该节点值为5,大于其右子树的值,不需要交换
在这里插入图片描述
在这里插入图片描述
下一步,继续找到下一个非叶子节点,该节点的值为7,大于其左子树的值,不需要交换,再看右子树,该节点的值小于右子树的值,需要交换值
在这里插入图片描述
在这里插入图片描述
下一步,检查调整后的子树,是否满足大顶堆性质,如果不满足则继续调整(这里因为只将右子树的值与根节点互换,只需要检查右子树是否满足,而7>2刚好满足大顶堆的性质,就不需要调整了。

如果运气不好整个树的根节点的值是1,那么就还需要调整右子树)

到这里大顶堆的构建就算完成了,然后下一步交换根节点(8)与最后一个元素(2)交换位置(将最大元素"沉"到数组末端),此时最大的元素就归位了,然后对剩下的5个元素重复上面的操作
在这里插入图片描述
(注:这里用粉红色来表示已经通过交换后归位的元素)

剩下只有5个元素,最后一个非叶子节点是5/2-1=1,该节点的值(5)大于左子树的值(3)也大于右子树的值(1),满足大顶堆性质不需要交换
在这里插入图片描述
找到下一个非叶子节点,该节点的值(2)小于左子树的值(5),交换值,交换后左子树不再满足大顶堆的性质再调整左子树,左子树满足要求后再返回去看根节点,根节点的值(5)小于右子树的值(7),再次交换值
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
得到新的大顶堆,如下图,再把根节点的值(7)与当前数组最后一个元素值(1)交换,再 重建大顶堆→交换值→重建大顶堆→交换值…,直到整个数组都变成有序序列
在这里插入图片描述
在这里插入图片描述
最后得到的升序序列如下图:
在这里插入图片描述

七、堆排序的代码实现

堆排序 Go版本实现:
另外,各种排序实现参考:常用各种sort排序实现(Go/C++版本)

// 堆排(大顶堆:每个节点的值都大于或等于其左右孩子节点的值)
func HeapSort(arr []int) {
	var CreateHeap = func(arr []int, i, length int) {
		tmp := arr[i]
 
		// 注意for循环条件:是 j<length 而不是 j<len(arr)
		for j := 2*i + 1; j < length; j = j*2 + 1 { // j=2i+1:当前根节点的左孩子下标 j= 2*j + 1:以当前叶子节点为新根节点,该新根节点的下一层叶子节点左孩子下标
			if j+1 < length && arr[j] < arr[j+1] { // j+1<length:右孩子(j+1)不能超出len长度范围
				j++
			}
 
			if tmp > arr[j] { // 左右孩子节点中选较大的节点值,并与父节点比较大小
				break // 若父节点值满足"大于或等于其左右孩子节点的值"则break,否则与较大的孩子节点相互交换
			}
 
			arr[i] = arr[j]
			i = j
		}
		arr[i] = tmp // 将最终比较后较小值放到合适的位置
	}
 
	// 首次构建堆
	l := len(arr)
	for i := l / 2; i >= 0; i-- { // 从二叉树最后一个父节点从底向上遍历(最上面的父节点:i = 0;最后一个父节点下标:i = len(arr) / 2)
		CreateHeap(arr, i, l)
	}
 
	// 再次重建堆
	for i := l - 1; i > 0; i-- { // 从下往上不断在每轮循环中置换出当前最大值,arr长度i也逐渐减到0
		arr[0], arr[i] = arr[i], arr[0] // swap 把大顶堆根节点(下标为0)上的最大值交换到末尾,置换出来.
		CreateHeap(arr, 0, i)
	}
}

func main() {
	array := []int{5, 28, 73, 19, 6, 0, 5}
	HeapSort(array)
	fmt.Println(array)
	return
}

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

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

相关文章

如此简单易懂的方式 让网站支持PWA

总结起来&#xff0c;网站配置PWA简单步骤为&#xff1a; 1.编写 manifest.json&#xff1b; 2.编写 serviceWorker.js&#xff1b; 3.在 index.html 引入上述两个文件&#xff1b; 4.把上述三个文件放在网站根目录(或者同一目录下)&#xff1b; 5.网站需要部署在https环境才能…

mannose-PEG-Alkyne|甘露糖-聚乙二醇-巯基|巯基修饰甘露糖

mannose-PEG-Alkyne|甘露糖-聚乙二醇-巯基|巯基修饰甘露糖 中文名称&#xff1a;甘露糖-巯基 英文名称&#xff1a;mannose-SH 别称&#xff1a;巯基修饰甘露糖&#xff0c;巯基-甘露糖 西安齐岳生物还可以提供PEG接枝修饰甘露糖&#xff0c;mannose-PEG-Alkyne 甘露糖-聚乙…

Android App开发实战项目之给用户推荐旅游信息图片(附源码 简单易懂)

需要全部源码请点赞关注收藏后评论区留言~~~ 一、需求描述 假定用户打开一个旅游App想看看哪里风景比较优美&#xff0c;那么App应当展示各地的风景名声图片&#xff0c;为了让界面不太呆板&#xff0c;可以考虑交错显示风景图片&#xff0c;接着用户向下拉动页面&#xff0c;…

【ROS】机械人开发二--ROS环境安装

机械人开发二--ROS环境安装一、运行环境二、ROS-melodic安装2.1 设置软件源2.2 设置密钥2.3 安装ROS2.4 环境设置2.5 安装ROS的依赖环境2.6 初始化rosdep三、建立工作空间测试一、运行环境 树莓派4B-4G、VMware15系统都为ubuntu18.04xshell 使用时&#xff0c;通过xshell同时…

10-1.WPF模板

10-1.WPF模板 控件由“算法内容”和“数据内容”决定 算法内容&#xff1a;指控件能展示哪些数据、具有哪些方法、能激发什么事件等&#xff0c;简而言之是控件的功能&#xff0c;一组相关逻辑数据内容&#xff1a;控件所展示的具体数据是什么 在WPF中&#xff0c;模板将数据…

Redis数据类型

1.String(字符串) 在任何一种编程语言中&#xff0c;字符串都是最基础的数据结构&#xff0c;在Redis中String是可以修改的称之为&#xff1a;动态字符串(简称SDS) Redis的内存分配机制&#xff1a; - 当字符串的长度小于1MB时&#xff0c;每次扩容都是加倍现有的空间 - 如果字…

离散数学:图的基本概念

本帖子讨论图的基本概念&#xff0c;这一章&#xff0c;我们将利用有序对和二元关系的概念定义图。图分为了无向图和有向图&#xff0c;他们有共性也有区别&#xff0c;请大家注意体会&#xff0c;用联系和辩证的观点去认识。 1、无向图和有向图 注意无向图和有向图的表示&…

Servlet【 ServletAPI中的会话管理Cookie与Session】

Servlet【 ServletAPI中的会话管理Cookie与Session】&#x1f352;一.回顾Cookie与Session&#x1f34e;1.1 Cookie&#x1f34e;1.2 Session&#x1f34e;1.3Cookie 和 Session 的区别&#x1f352;二.Servlet会话管理操作&#x1f34e;2.1核心方法&#x1f352;三.常见案例实…

【服务器搭建】教程一:没钱买服务器怎么玩 进来看

前言&#xff1a; 最近看到有一些网上的大佬把自己的爱心网页&#xff08;没领到的小伙伴看一下前几篇文章&#xff09;部署到了自己的服务器上&#xff0c;使得可以直接通过链接就实现访问。属实不错&#xff01; 自己内心就产生了这样一个想法&#xff1a;购买一台服务器&a…

Whisper论文阅读笔记

Whisper论文阅读笔记Robust Speech Recognition via Large-Scale Weak Supervision1. 引言2. 方法2.1 数据处理2.2 模型2.3 多任务设置2.4 训练细节3. 实验结果3.1 Zero-shot3.2 多语言语音识别3.3 多语言机器翻译3.4 语种检测3.5 对加性噪声的鲁棒性3.6 长语音转录3.7 人类基线…

基于向量加权平均值的高效优化算法(Matlab代码实现)

&#x1f4dd;个人主页&#xff1a;研学社的博客 &#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;…

学习 RabbitMQ 这一篇就够了

文章目录一、MQ相关概念二、RabbitMQ相关概念三、安装四、HelloWorld五、工作队列5.1、轮询分发消息5.2、消息应答5.2.1、自动应答5.2.2、手动应答5.2.3、消息自动重新入队5.2.4、消息手动应答代码5.3、持久化5.4、不公平分发&#xff08;能者多劳&#xff09;5.5、预取值5.6、…

读书笔记3|使用Python,networkx对卡勒德胡赛尼三部曲之——《群山回唱》人物关系图谱绘制

读书笔记3|《群山回唱》-卡勒德胡赛尼 踉跄前行中&#xff0c;你总能在他们身上找到丢失的那一部分记忆。 一度看不下去这本书&#xff0c;因为最开始的章节里太痛了&#xff0c;加上我也离开我的孩子&#xff0c;生活已经够苦&#xff0c;我需要一点糖。这次实在太无聊了&…

php-上传图片加水印(文字水印图片水印)

img.php <?php $img 1.jpg; //获取图片信息 $info getimagesize($img); //获取图片类型 $type image_type_to_extension($info[2],false); //在内容中创建一个和图片一模一样的图片 $ext "imagecreatefrom{$type}"; //图片复制到内存中 $image $ext($img);…

基于SpringBoot前后端分离的网吧管理系统

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SpringBoot 前端&#xff1a;采用Vue技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目…

PICO《轻世界》体验:随心畅玩,洒脱创作,潜力无限

不少玩家应该还记得&#xff0c;PICO 4发布会上曾宣布将在VR运动健身、VR视频、VR娱乐、VR创造四大方向展开内容布局。而目前&#xff0c;前三个完成了基本部署&#xff0c;在创造方向上则依托于刚刚上线的《轻世界》这款应用。《轻世界》是一款3D内容UGC创作产品&#xff0c;目…

php宝塔部署实战thinkphp考试平台管理系统源码

大家好啊&#xff0c;我是测评君&#xff0c;欢迎来到web测评。 有个朋友发消息跟我说&#xff0c;在网上下载了一套thinkphp考试管理系统的源码&#xff0c;在搭建的时候遇到问题一直部署不起来&#xff0c;让我帮他看看&#xff0c;我看了下代码&#xff0c;里面有些部分代码…

2022年11月华南师范大学自考本科网络工程-本科实践题目

《互联网及其应用&#xff08;03142&#xff09;&#xff08;实践&#xff09;》课程试卷 答卷提交说明&#xff1a;编程代码与输出结果截图&#xff0c;放到一个文件中&#xff0c;文件以“序号 姓名 课程名 ”命名&#xff0c;本试卷有三门课程&#xff0c;请根据不同的课程…

k8s训练营

一、linux命名空间和docker 1.linux的7大ns--------------ipc,net,pid,mnt.uts.user 查看linux的ns lsns查看不同类型的ns [rootmaster ~]# lsns -t netNS TYPE NPROCS PID USER COMMAND 4026531956 net 116 1 root /usr/lib/systemd/systemd --system --deserialize …

公司代码全局参数设置及其意义

在SAP中配置公司时&#xff0c;会配置公司的全局参数&#xff0c;但这些参数具体的意思是什么估计很多同学都搞不懂&#xff0c;我也找了下资料&#xff0c;贴出来供大家参考。 设置参数路径&#xff1a;IMG→财务会计→财务会计全局设置→公司代码的全球参数→输入全局参数 账…