堆排序(Heap Sort)是一种基于二叉堆数据结构的高效排序算法,由计算机科学家 J. W. J. Williams 于 1964 年提出。它结合了选择排序的思想和二叉堆的特性,具有时间复杂度稳定(O (nlogn))、原地排序(空间复杂度 O (1)) 等优点,在大规模数据排序场景中应用广泛。

堆的基本概念与性质 🎂

二叉堆的定义​

二叉堆是一种完全二叉树(除最后一层外,每层节点均满,最后一层节点靠左排列),分为两种类型:​

最大堆:每个父节点的值大于等于其左右子节点的值(parent.val ≥ left.val 且 parent.val ≥ right.val)。​
最小堆:每个父节点的值小于等于其左右子节点的值(parent.val ≤ left.val 且 parent.val ≤ right.val)。​ 堆排序中通常使用最大堆,本文以最大堆为例讲解。

堆的存储结构 🎂

堆通常用数组实现,利用完全二叉树的性质映射节点索引(假设数组索引从 0 开始):​

对于节点 i:​
左子节点索引:2i + 1
​右子节点索引:2i + 2​
父节点索引:(i - 1) / 2(整数除法)

在这里插入图片描述

构建最大堆​

构建最大堆需从最后一个非叶子节点开始,依次向前执行堆调整操作。最后一个非叶子节点的索引为 (n / 2) - 1(n 为数组长度)。

构建过程代码:

private void buildMaxHeap(int[] arr) {int n = arr.length;// 从最后一个非叶子节点开始调整for (int i = (n / 2) - 1; i >= 0; i--) {maxHeapify(arr, n, i);}
}

堆排序完整实现​ 🎂

算法流程​

调用 buildMaxHeap 将数组转为最大堆。​
初始化堆大小 heapSize = n。​
循环 n - 1 次:​
交换堆顶(arr[0])与堆尾(arr[heapSize - 1])元素。​
减小堆大小(heapSize–)。​
调用 maxHeapify 调整堆顶元素,维护剩余元素的堆性质。

堆排序图示

堆排序图示

程序代码:

public class HeapSort {public void sort(int[] arr) {int n = arr.length;if (n <= 1) return;// 步骤1:构建最大堆buildMaxHeap(arr);// 步骤2:排序阶段int heapSize = n;for (int i = n - 1; i > 0; i--) {// 交换堆顶与当前堆尾swap(arr, 0, i);heapSize--; // 堆大小减1// 调整剩余元素为最大堆maxHeapify(arr, heapSize, 0);}}private void buildMaxHeap(int[] arr) {int n = arr.length;for (int i = (n / 2) - 1; i >= 0; i--) {maxHeapify(arr, n, i);}}private void maxHeapify(int[] arr, int heapSize, int i) {int left = 2 * i + 1;int right = 2 * i + 2;int maxIndex = i;if (left < heapSize && arr[left] > arr[maxIndex]) {maxIndex = left;}if (right < heapSize && arr[right] > arr[maxIndex]) {maxIndex = right;}if (maxIndex != i) {swap(arr, i, maxIndex);maxHeapify(arr, heapSize, maxIndex);}}private void swap(int[] arr, int i, int j) {int temp = arr[i];arr[i] = arr[j];arr[j] = temp;}public static void main(String[] args) {int[] arr = {4, 1, 3, 2, 16, 9, 10, 14, 8, 7};HeapSort heapSort = new HeapSort();heapSort.sort(arr);System.out.println(Arrays.toString(arr)); // 输出:[1, 2, 3, 4, 7, 8, 9, 10, 14, 16]}
}

复杂度分析​

时间复杂度:​
构建最大堆:O (n)(严格证明需用到堆的高度求和,结果为 O (n))。​
排序阶段:共执行 n-1 次堆调整,每次调整为 O (logn),总时间 O (nlogn)。​
整体时间复杂度:O (nlogn),且最坏情况下仍为 O (nlogn),稳定性优于快速排序。​
空间复杂度:O (1),原地排序,仅需常数级额外空间。

LeetCode 例题实战​ 🎂

例题 1:912. 排序数组(中等)​

题目描述:给你一个整数数组 nums,请你将该数组升序排列。


示例:
输入:nums = [5,2,3,1]​
输出:[1,2,3,5]

代码实现

class Solution {public int[] sortArray(int[] nums) {if (nums == null || nums.length <= 1) {return nums;}heapSort(nums);return nums;}private void heapSort(int[] arr) {int n = arr.length;// 构建最大堆for (int i = (n / 2) - 1; i >= 0; i--) {maxHeapify(arr, n, i);}// 排序阶段for (int i = n - 1; i > 0; i--) {swap(arr, 0, i);maxHeapify(arr, i, 0);}}private void maxHeapify(int[] arr, int heapSize, int i) {int left = 2 * i + 1;int right = 2 * i + 2;int maxIndex = i;if (left < heapSize && arr[left] > arr[maxIndex]) {maxIndex = left;}if (right < heapSize && arr[right] > arr[maxIndex]) {maxIndex = right;}if (maxIndex != i) {swap(arr, i, maxIndex);maxHeapify(arr, heapSize, maxIndex);}}private void swap(int[] arr, int i, int j) {int temp = arr[i];arr[i] = arr[j];arr[j] = temp;}
}

例题 2:215. 数组中的第 K 个最大元素(中等)​

题目描述:给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。​

示例
输入: [3,2,1,5,6,4] 和 k = 2​
输出: 5

解题思路​

利用最大堆的堆顶为最大值的特性,弹出k-1个最大值后,堆顶即为第 k 个最大元素。或使用最小堆(更高效),维护大小为 k 的最小堆,堆顶为第 k 个最大元素。
方法 2(最小堆)Java 代码

class Solution {public int findKthLargest(int[] nums, int k) {// 最小堆,堆顶为第k大元素PriorityQueue<Integer> minHeap = new PriorityQueue<>(k);for (int num : nums) {if (minHeap.size() < k) {minHeap.add(num); // 堆未满,直接加入} else if (num > minHeap.peek()) {// 元素大于堆顶,替换堆顶并调整minHeap.poll();minHeap.add(num);}}return minHeap.peek(); // 堆顶为第k大元素}
}

复杂度分析​
时间复杂度:O (nlogk),插入 n 个元素,每次堆调整为 O (logk)。​
空间复杂度:O (k),堆存储 k 个元素。

考研 408 例题解析​ 🎂

例题 1:概念辨析题(选择题)​

题目:下列关于堆的叙述中,正确的是( )。
​A. 最大堆中,从根节点到任意叶子节点的路径上的元素是递减的​
B. 堆排序是稳定的排序算法​
C. 构建最大堆的时间复杂度为 O (nlogn)​
D. 堆的调整操作(Heapify)的时间复杂度为 O (n)​

答案:A​
解析:​
A 正确:最大堆中,父节点的值大于等于子节点,因此根到叶子的路径元素递减。​
B 错误:堆排序中交换元素可能改变相等元素的相对顺序(如 [2,2] 排序后可能颠倒),因此不稳定。​
C 错误:构建最大堆的时间复杂度为 O (n),而非 O (nlogn)。​
D 错误:堆的调整操作时间复杂度为 O (logn),与树的高度相关。​

例题 2:算法设计题(408 高频考点)​

题目:设计一个算法,利用堆实现一个优先级队列,支持插入元素和提取最大元素操作,并分析两种操作的时间复杂度。​

解题思路​
优先级队列结构:用数组存储堆,维护最大堆性质。​
插入操作:​
将新元素添加到堆尾。​
执行 “上浮” 操作:与父节点比较,若大于父节点则交换,直至根节点或小于父节点。​
提取最大元素操作:​
取出堆顶元素(最大值)。​
将堆尾元素移至堆顶。​
执行 “下沉” 操作(即堆调整),维护最大堆性质。

Java 代码实现

public class MaxPriorityQueue {private int[] heap;private int size; // 当前元素个数private int capacity; // 队列容量public MaxPriorityQueue(int capacity) {this.capacity = capacity;heap = new int[capacity + 1]; // 1-based索引,便于计算子节点size = 0;}// 插入元素public void insert(int val) {if (size == capacity) {throw new IllegalStateException("队列已满");}size++;heap[size] = val;swim(size); // 上浮调整}// 提取最大元素public int extractMax() {if (size == 0) {throw new NoSuchElementException("队列为空");}int max = heap[1];swap(1, size); // 交换堆顶与堆尾size--;sink(1); // 下沉调整return max;}// 上浮操作(用于插入)private void swim(int k) {while (k > 1 && heap[k] > heap[k / 2]) { // 与父节点比较swap(k, k / 2);k = k / 2;}}// 下沉操作(用于提取最大元素)private void sink(int k) {while (2 * k <= size) { // 存在左子节点int j = 2 * k; // 左子节点if (j < size && heap[j] < heap[j + 1]) { // 右子节点更大j++;}if (heap[k] >= heap[j]) {break; // 已满足最大堆性质}swap(k, j);k = j;}}private void swap(int i, int j) {int temp = heap[i];heap[i] = heap[j];heap[j] = temp;}
}

复杂度分析​

插入操作:时间复杂度 O (logn),上浮操作最多执行 logn 次(树的高度)。​
提取最大元素:时间复杂度 O (logn),下沉操作最多执行 logn 次。​

堆排序的扩展与应用​ 🎂

实际应用场景​

优先级队列:操作系统的进程调度、任务队列等,需频繁获取最大值 / 最小值。​
Top-K 问题:如获取海量数据中前 k 个最大元素(如 LeetCode 215 题)。​
流数据处理:实时处理数据流,维护动态数据的 Top-K 元素。​
堆排序:适用于大规模数据排序,尤其是内存有限的场景(原地排序)。​

与其他排序算法的对比

排序算法平均时间复杂度最坏时间复杂度空间复杂度
堆排序O(nlogn)O(nlogn)O(1)
快速排序O(nlogn)O(n²)O(logn)
归并排序O(nlogn)O(nlogn)O(n)

考研 408 备考要点​ 🎂

核心考点:堆的性质、堆的构建与调整、堆排序的步骤与复杂度。​

重点掌握:​

堆的索引计算(父节点与子节点的关系)。​
堆调整(Heapify)的递归与非递归实现。​
堆排序与优先级队列的关联,以及 Top-K 问题的解法。​

常见错误:​

混淆最大堆与最小堆的调整逻辑。​
构建堆时从 0 开始而非最后一个非叶子节点。​
忽略堆排序的不稳定性,错误认为其稳定。​

总结​ 🎂

堆排序作为一种高效的排序算法,凭借 O (nlogn) 的稳定时间复杂度和原地排序的特性,在大规模数据处理中有着不可替代的地位。本文从堆的基本概念出发,详细讲解了堆的构建、调整操作和堆排序的完整流程,结合 LeetCode 例题(排序数组、Top-K 问题)展示了堆的核心应用,通过考研 408 例题解析了概念辨析和算法设计思路,并穿插 SVG 图示直观呈现了堆的结构与操作过程。​
掌握堆排序的关键在于:​
深刻理解堆的性质及索引映射关系。​
熟练实现堆的调整(Heapify)、构建和排序过程。​
灵活运用堆解决优先级队列、Top-K 等实际问题。​
在考研备考中,堆的性质、堆排序的复杂度分析以及优先级队列的实现是重点,需结合具体例题深入练习,理解堆在数据结构中的核心作用。

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

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

I/O 多路复用select,poll

目录 I/O多路复用的介绍 多进程/多线程模型的弊端 网络多路复用如何解决问题&#xff1f; 网络多路复用的常见实现方式 常见的开源网络库 select详细介绍 select函数介绍 套接字可读事件,可写事件,异常事件 fd_set类型介绍 select的两次拷贝&#xff0c;两次遍历 se…

最终分配算法【论文材料】

文章目录一、最终分配算法1.1 平衡的情况1.2 不平衡的情况1.3 TDM 约束一、最终分配算法 上一步合法化后&#xff0c;group 的 TDM 情况大致分为两类&#xff0c;一类是平衡的&#xff0c;最大的一些 group 的 TDM 比较接近。另外一种情况就是不平衡的&#xff0c;最大的 group…

《大数据技术原理与应用》实验报告七 熟悉 Spark 初级编程实践

目 录 一、实验目的 二、实验环境 三、实验内容与完成情况 3.1 Spark读取文件系统的数据。 3.2 编写独立应用程序实现数据去重。 3.3 编写独立应用程序实现求平局值问题。 四、问题和解决方法 五、心得体会 一、实验目的 1. 掌握使用 Spark 访问本地文件和 HDFS 文件的…

机器学习漫画小抄 - 彩图版

斯坦福机器学习漫画小抄&#xff0c;中文版来啦&#xff01; 下载地址&#xff1a; 通过网盘分享的文件&#xff1a;机器学习知识点彩图版.pdf 链接: https://pan.baidu.com/s/1-fH9OpC_u_OrTqWy6gVUCA 提取码: 246r

1.初始化

业务模块核心技术栈业务&#xff08;亮点&#xff09;解决方案课程安排01 认识Vue3为什么需要学Vue3?Vue3组合式API体验Vue3更多的优势2 使用create-vue搭建Vue3项目认识 create-vue使用create-vue创建项目3 熟悉项目目录和关键文件项目目录和关键文件4 组合式API - setup选项…

Milvus分布式数据库工作职责

主导腾讯云Milvus服务化项目&#xff0c;设计多租户隔离方案&#xff0c;支撑日均10亿向量请求&#xff0c;延迟降低40%。优化IVF_PQ索引构建流程&#xff0c;通过量化编码压缩使内存占用减少60%&#xff0c;QPS提升35%。开发基于Kubernetes的Milvus Operator&#xff0c;实现自…

FMEA-CP-PFD三位一体数字化闭环:汽车部件质量管控的速效引擎

FMEA-CP-PFD三位一体数字化闭环&#xff1a;汽车部件质量管控的速效引擎 全星FMEA软件系统通过​​FMEA&#xff08;失效模式分析&#xff09;、CP&#xff08;控制计划&#xff09;、PFD&#xff08;过程流程图&#xff09;三大工具的一体化协同管理​​&#xff0c;为汽车部件…

VUE2 学习笔记1

目录 VUE特点 文档tips 开发者工具 从一个Hello world开始 hello world Demo 容器和实例的对应关系 差值语法{{}} VUE特点 构建用户界面&#xff1a;可以用来把数据构建成用户界面。 渐进式&#xff1a;自底向上&#xff0c;可以先从一个非常轻量级的框架开始&#xf…

嵌入式学习系统编程(四)进程

目录 一、进程 1.程序和进程 2.进程的八种状态 3. 几个状态 4.关于进程常用命令 二、关于进程的函数 1.fork 2.面问 3.孤儿进程 后台进程 2. exec函数族 (只保留父子关系&#xff0c;做新的事情) strtok函数 三、进程的结束 1.分类 exit和_exit的区别 wait函数…

Linux中添加重定向(Redirection)功能到minishell

前言&#xff1a;在谈论添加minishell之前&#xff0c;我再重谈一下重定向的具体实现等大概思想&#xff01;&#xff01;&#xff01;方便自己回顾&#xff01;&#xff01;&#xff01; 目录 一、重定向&#xff08;Redirection&#xff09;原理详解 1、文件描述符基础 2、…

Django由于数据库版本原因导致数据库迁移失败解决办法

在django开发中&#xff0c;一般我们初始化一个项目之后&#xff0c;创建应用一般就会生成如下的目录&#xff1a;django-admin startproject myproject python manage.py startapp blogmyproject/ ├── manage.py └── myproject/ | ├── __init__.py | ├── se…

C++STL系列之vector

前言 vector是变长数组&#xff0c;有点像数据结构中的顺序表&#xff0c;它和list也是经常被拿出作对比的&#xff0c; vector使用动态分配数组来存储它的元素。当新元素插入时候&#xff0c;这个数组需要被重新分配大小&#xff0c;如果扩容&#xff0c;因为要开一个新数组把…

Functional C++ for Fun Profit

Lambda Conf上有人讲C函数式编程。在Functional Conf 2019上&#xff0c;就有主题为“Lambdas: The Functional Programming Companion of Modern C”的演讲。演讲者介绍了现代C中函数式编程相关内容&#xff0c;讲解了如何使用Lambda表达式编写符合函数式编程原则的C代码&…

Python基础理论与实践:从零到爬虫实战

引言Python如轻舟&#xff0c;载你探寻数据宝藏&#xff01;本文从基础理论&#xff08;变量、循环、函数、模块&#xff09;启航&#xff0c;结合requests和BeautifulSoup实战爬取Quotes to Scrape&#xff0c;适合零基础到进阶者。文章聚焦Python基础&#xff08;变量、循环、…

ThingJS开发从入门到精通:构建三维物联网可视化应用的完整指南

文章目录第一部分&#xff1a;ThingJS基础入门第一章 ThingJS概述与技术架构1.1 ThingJS平台简介1.2 技术架构解析1.3 开发环境配置第二章 基础概念与核心API2.1 核心对象模型2.2 场景创建与管理2.3 对象操作基础第三章 基础开发实战3.1 第一个ThingJS应用3.2 事件系统详解3.3 …

关于list

1、什么是listlist是一个带头结点的双向循环链表模版容器&#xff0c;可以存放任意类型&#xff0c;需要显式定义2、list的使用有了前面学习string和vector的基础&#xff0c;学习和使用list会方便很多&#xff0c;因为大部分的内容依然是高度重合的。与顺序表不同&#xff0c;…

Mysql 查看当前事务锁

在 MySQL 中查看事务锁&#xff08;锁等待、锁持有等&#xff09;&#xff0c;可以使用以下方法&#xff1a; 一、查看当前锁等待情况&#xff08;推荐&#xff09; SELECTr.trx_id AS waiting_trx_id,r.trx_mysql_thread_id AS waiting_thread,r.trx_query AS waiting_query,b…

【Keil5-map文件】

Keil5-map文件■ map文件■ map文件

k8s 基本架构

基于Kubernetes(K8s)的核心设计&#xff0c;以下是其关键基本概念的详细解析。这些概念构成了K8s容器编排系统的基石&#xff0c;用于自动化部署、扩展和管理容器化应用。### 一、K8s核心概念概览 K8s的核心对象围绕容器生命周期管理、资源调度和服务发现展开&#xff0c;主要包…

Bell不等式赋能机器学习:微算法科技MLGO一种基于量子纠缠的监督量子分类器训练算法技术

近年来&#xff0c;量子计算&#xff08;Quantum Computing&#xff09; 和 机器学习&#xff08;Machine Learning&#xff09; 的融合成为人工智能和计算科学领域的重要研究方向。随着经典计算机在某些复杂任务上接近计算极限&#xff0c;研究人员开始探索量子计算的独特优势…