常见排序算法总结 (五) - 堆排序与堆操作
堆排序(借助 API)
算法思想
利用堆能够维护数组中最大值的性质,根据数组元素建立最大堆,依次弹出元素并维护堆结构,直到堆为空。
稳定性分析
堆排序是不稳定的,因为堆本质上是完全二叉树,排序的过程涉及二叉树的父子节点交换,没有办法保证办法保证相等的值一定在同一棵子树上被处理。
具体实现
// Java 本身实现了优先队列的 API,其本质类似于堆,可以用来实现堆排序
private void heapSort(int[] arr) {Queue<Integer> queue = new PriorityQueue<>();for(int item : arr) {queue.offer(item);}for(int i = 0; i < arr.length; i++) {arr[i] = queue.poll();}
}
堆操作
上面的堆排序实现,有一种脑干缺失的美,图一乐就行。堆相关的内容中,堆的原理和操作显然更重要。
自顶向底建堆
自顶向底建堆,时间复杂度是 O ( N l o g N ) O(NlogN) O(NlogN) 这个量级的。自下而上的调整操作实现起来比较简单,原因是对于每个子节点而言,父节点是唯一的。
// 交换数组中的两个元素
private void swap(int[] arr, int i, int j) {int temp = arr[i];arr[i] = arr[j];arr[j] = temp;
}// 由下而上调整元素
private void upAdjust(int[] arr, int cur) {while (arr[cur] > arr[(cur - 1) / 2]) {// 当前元素大于父节点,那么进行交换并移动工作指针swap(arr, cur, (cur - 1) / 2);cur = (cur - 1) / 2;}
}// 自顶向底建堆
private void buildHeap(int[] arr) {for (int i = 0; i < arr.length; i++) {upAdjust(arr, i);}
}
自底向顶建堆
自底向顶建堆,它的时间复杂度是 O ( N ) O(N) O(N) 这个量级的。实现相对来说要更麻烦,原因是父节点可能有两个子节点,涉及到与谁交换的判断。
// 交换数组中的两个元素
private void swap(int[] arr, int i, int j) {int temp = arr[i];arr[i] = arr[j];arr[j] = temp;
}// 由上而下调整元素
private void downAdjust(int[] arr, int cur, int size) {// 数组下标从零开始,当前节点的左孩子下标为 2 * cur + 1int child = 2 * cur + 1;while (child < size) {// 如果当前节点有右孩子,那么比较两个子节点的值确定潜在的交换对象int target = child + 1 < size && arr[child + 1] > arr[child] ? child + 1 : child;// 再与当前节点比较大小target = arr[target] > arr[cur] ? target : cur;// 一旦发现此次操作中无需交换,立即停止流程if (target == cur) {break;}// 交换父子节点swap(arr, target, cur);// 移动工作指针cur = target;child = 2 * cur + 1;}
}// 自底向顶建堆
private void buildHeap(int[] arr) {int n = arr.length;for (int i = n - 1; i >= 0; i--) {downAdjust(arr, i, n);}
}
堆排序(自己实现)
自顶向底建堆并实现排序
// 交换数组中的两个元素
private void swap(int[] arr, int i, int j) {int temp = arr[i];arr[i] = arr[j];arr[j] = temp;
}// 由下而上调整元素
private void upAdjust(int[] arr, int cur) {while (arr[cur] > arr[(cur - 1) / 2]) {// 当前元素大于父节点,那么进行交换并移动工作指针swap(arr, cur, (cur - 1) / 2);cur = (cur - 1) / 2;}
}// 自顶向底建堆
private void buildHeap(int[] arr) {for (int i = 0; i < arr.length; i++) {upAdjust(arr, i);}
}// 由上而下调整元素
private void downAdjust(int[] arr, int cur, int size) {// 数组下标从零开始,当前节点的左孩子下标为 2 * cur + 1int child = 2 * cur + 1;while (child < size) {// 如果当前节点有右孩子,那么比较两个子节点的值确定潜在的交换对象int target = child + 1 < size && arr[child + 1] > arr[child] ? child + 1 : child;// 再与当前节点比较大小target = arr[target] > arr[cur] ? target : cur;// 一旦发现此次操作中无需交换,立即停止流程if (target == cur) {break;}// 交换父子节点swap(arr, target, cur);// 移动工作指针cur = target;child = 2 * cur + 1;}
}// 堆排序
private void heapSort(int[] arr) {buildHeap(arr);int size = arr.length;// 不断地交换堆顶元素与堆中的最后一个元素,并向下调整维护堆while (size > 0) {swap(arr, 0, --size);downAdjust(arr, 0, size);}
}
自底向顶建堆并实现排序
// 交换数组中的两个元素
private void swap(int[] arr, int i, int j) {int temp = arr[i];arr[i] = arr[j];arr[j] = temp;
}// 由上而下调整元素
private void downAdjust(int[] arr, int cur, int size) {// 数组下标从零开始,当前节点的左孩子下标为 2 * cur + 1int child = 2 * cur + 1;while (child < size) {// 如果当前节点有右孩子,那么比较两个子节点的值确定潜在的交换对象int target = child + 1 < size && arr[child + 1] > arr[child] ? child + 1 : child;// 再与当前节点比较大小target = arr[target] > arr[cur] ? target : cur;// 一旦发现此次操作中无需交换,立即停止流程if (target == cur) {break;}// 交换父子节点swap(arr, target, cur);// 移动工作指针cur = target;child = 2 * cur + 1;}
}// 自底向顶建堆
private void buildHeap(int[] arr) {int n = arr.length;for (int i = n - 1; i >= 0; i--) {downAdjust(arr, i, n);}
}// 堆排序
private void heapSort(int[] arr) {buildHeap(arr);int size = arr.length;// 不断地交换堆顶元素与堆中的最后一个元素,并向下调整维护堆while (size > 0) {swap(arr, 0, --size);downAdjust(arr, 0, size);}
}
梳理总结
堆排序主要有两大步骤,包括建堆和出堆排序,其中建堆的操作根据方向的不同有效率上的差异,但是因为出堆排序需要 O ( N l o g N ) O(NlogN) O(NlogN) 量级的时间,所以总的时间复杂度为 O ( N l o g N ) O(NlogN) O(NlogN)。
在实现的选择上,虽然自顶向底建堆本身相对比较容易实现,但是由于出堆排序的过程中一定会涉及到由上而下的调整,反而需要记忆更多的内容。因此,可以考虑只记住自底向顶建堆的实现方法。
事实上鉴于堆排序不具有稳定性,性能上也只是中规中矩,所以通常只有在考试遇到、要求实现不使用额外空间的情况下(随机快排需要额外的递归栈空间,大约是 O ( l o g N ) O(logN) O(logN) 的水平;归并需要额外的辅助数组,是 O ( N ) O(N) O(N) 的水平),会手写实现堆排序。
而在实际应用的过程中,空间换时间是常见操作,所以不需要额外空间的堆并没有什么优势。
堆本身可以维护数组中最大最小值的性质是非常美妙的,一般来说直接调用语言本身提供的 API 即可,例如 C++ 的 STL 和 Java 中都提供了优先队列。
后记
使用 Leetcode 912. 排序数组 进行测试,堆排序能够比较高效地完成任务,大致与随机快速排序相当。
相关阅读
- 常见排序算法总结 (一) - 三种基本排序
- 常见排序算法总结 (二) - 不基于比较的排序
- 常见排序算法总结 (三) - 归并排序与归并分治
- 常见排序算法总结 (四) - 快速排序与随机选择
相关文章:
常见排序算法总结 (五) - 堆排序与堆操作
堆排序(借助 API) 算法思想 利用堆能够维护数组中最大值的性质,根据数组元素建立最大堆,依次弹出元素并维护堆结构,直到堆为空。 稳定性分析 堆排序是不稳定的,因为堆本质上是完全二叉树,排…...
kubernetes的三种探针ReadinessProbe、LivenessProbe和StartupProbe,以及使用示例
前言 k8s中的Pod由容器组成,容器运行的时候可能因为意外情况挂掉。为了保证服务的稳定性,在容器出现问题后能进行重启,k8s提供了3种探针 k8s的三种探针 为了探测容器状态,k8s提供了两个探针: LivenessProbe和ReadinessProbe L…...
掌握线性回归:从简单模型到多项式模型的综合指南
目录 一、说明 二、简单线性回归 三、线性回归的评估指标 3.1 线性回归中的假设 四、从头开始的简单线性回归代码 五、多元线性回归 六、多元线性回归代码 七、多项式线性回归 八、多项式线性回归代码 九、应用单变量多项式回归 十、改变多项式的次数 十一、多列多项式回归 一、…...
Java:183 基于SSM的高校食堂系统
项目介绍 基于SSM的食堂点餐系统 角色:管理员、用户、食堂 前台用户可以实现商品浏览,加入购物车,加入收藏,预定,选座,个人信息管理,收货信息管理,收藏管理,评论功能,…...
光谱相机
光谱相机是一种能够同时获取目标物体的空间图像信息和光谱信息的成像设备。 1、工作原理 光谱相机通过光学系统将目标物体的光聚焦到探测器上,在探测器前设置分光元件,如光栅、棱镜或滤光片等,将光按不同波长分解成多个光谱通道,…...
AI绘图:开源Stable Diffusion 3 ComfyUI下载安装方法
AI绘图:开源Stable Diffusion 3 ComfyUI下载安装方法 安装好后软件运行效果: 第一步:安装ComfyUI的最新版本 1、请从下面的地址下载压缩包,并解压缩到硬盘 https://github.com/comfyanonymous/ComfyUI/releases/download/late…...
一区向量加权算法优化INFO-CNN-SVM卷积神经网络结合支持向量机多特征分类预测
一区向量加权算法优化INFO-CNN-SVM卷积神经网络结合支持向量机多特征分类预测 目录 一区向量加权算法优化INFO-CNN-SVM卷积神经网络结合支持向量机多特征分类预测分类效果基本描述程序设计参考资料 分类效果 基本描述 1.Matlab实现INFO-CNN-SVM向量加权算法优化卷积神经网络结…...
AES笔记整理
文章目录 1. 简介2. 密钥加法层2. 字节代换层3. 行位移 - ShiftRows4. 列混淆 - MixColumn5. 其他5.1列混淆矩阵乘法运算5.2 AES密钥生成 6. 参考资料 以下内容为信息安全开发过程中,AES对称加密算法的笔记,大部分内容转载其他文章,若描述不清…...
Jmeter 性能压测-Tomcat连接数
1、影响性能的线程状态 ①BLOCKED,如果线程中有BLOCKED,就代表有阻塞情况,需要进行排查 ②TIMED_WAITING,如果线程中有TIMED_WAITING,就代表有等待的情况,要分情况来排查 系统线程在等待(如果…...
基于Vue3的组件封装技巧分享
1、需求说明 需求背景:日常开发中,我们经常会使用一些UI组件库诸如and design vue、element plus等辅助开发,提升效率。有时我们需要进行个性化封装,以满足在项目中大量使用的需求。 错误示范:基于a-modal封装一个自定…...
python中r代表什么意思
r在python中表示什么意思? “r”是“raw”的简写。去查单词,意思是“未加工的,原料”。因此,不难想象,在python字符串前面,表示“按原样输出字符串”,也就是说字符串里的元素,原来什…...
《量子计算对人工智能发展的深远影响》
在科技发展的浪潮中,量子计算与人工智能无疑是两颗璀璨的明星,二者的融合正引领着一场深刻的科技变革. 量子计算的独特之处在于其利用量子比特的叠加和纠缠特性,能够实现并行计算,从而在处理复杂问题时展现出超越传统计算的巨大潜…...
12.2【JAVA EXP4]next.js的各种问题,DEBUG,前端补强,前后端交互,springSecurity ,java 配置,h2数据库
在服务器组件中使用了 useState 这样的 React Hook。useState 只能在客户端组件中使用,而不能在服务器组件中使用。Next.js 的新架构(App Router)中,默认情况下,页面和布局组件是服务器组件,因此不能直接使…...
docker启动一个helloworld(公司内网服务器)
这里写目录标题 容易遇到的问题:1、docker连接问题 我来介绍几种启动 Docker Hello World 的方法: 最简单的方式: docker run hello-world这会自动下载并运行官方的 hello-world 镜像。 使用 Nginx 作为 Hello World: docker…...
使用 Netty 实现 RPC 通信框架
使用 Netty 实现 RPC 通信框架 远程过程调用(RPC,Remote Procedure Call) 是分布式系统中非常重要的通信机制。它允许客户端调用远程服务器上的方法,就像调用本地方法一样。RPC 的核心在于屏蔽底层通信细节,使开发者关…...
【机器学习06--贝叶斯分类器】
文章目录 基础理解01 贝叶斯决策论02 极大似然估计03 朴素贝叶斯分类器04 半朴素贝叶斯分类器05 贝叶斯网06 EM算法 补充修正1. 贝叶斯定理与分类的基本概念2. 贝叶斯决策论3. 极大似然估计4. 朴素贝叶斯分类器5. 半朴素贝叶斯分类器6. 贝叶斯网7. EM算法 面试常考 基础理解 本…...
创建vue3项目步骤以及安装第三方插件步骤【保姆级教程】
🎙座右铭:得之坦然,失之淡然。 💎擅长领域:前端 是的,我需要您的: 🧡点赞❤️关注💙收藏💛 是我持续下去的动力! 目录 一. 简单汇总一下创建…...
[146 LRU缓存](https://leetcode.cn/problems/lru-cache/)
分析 维护一个双向链表保存缓存中的元素。 如果元素超过容量阈值,则删除最久未使用的元素。为了实现这个功能,将get(), put()方法获取的元素添加到链表首部。 为了在O(1)时间复杂度执行get()方法,再新建一个映射表,缓存key与链表…...
【Java Nio Netty】基于TCP的简单Netty自定义协议实现(万字,全篇例子)
基于TCP的简单Netty自定义协议实现(万字,全篇例子) 前言 有一阵子没写博客了,最近在学习Netty写一个实时聊天软件,一个高性能异步事件驱动的网络应用框架,我们常用的SpringBoot一般基于Http协议࿰…...
【JavaWeb后端学习笔记】Redis常用命令以及Java客户端操作Redis
redis 1、redis安装与启动服务2、redis数据类型3、redis常用命令3.1 字符串String3.2 哈希Hash3.3 列表List3.4 集合Set(无序)3.5 有序集合zset3.6 通用命令 4、使用Java操作Redis4.1 环境准备4.2 Java操作字符串String4.3 Java操作哈希Hash4.4 Java操作…...
2026 BI指标管理平台设计与最佳实践
引言关于衡石科技(HENGSHI):衡石科技是国内领先的嵌入式BI PaaS平台提供商,其核心产品HENGSHI SENSE以"让数据分析无处不在"为使命,为企业提供从数据连接、数据准备、指标管理、可视化分析到智能问答的全链路…...
贵州方言语音AI落地难?从数据采集、音素映射到MOS评分提升至4.1的5步攻坚法
更多请点击: https://codechina.net 第一章:贵州方言语音AI落地难?从数据采集、音素映射到MOS评分提升至4.1的5步攻坚法 贵州方言语音AI落地长期受限于语料稀疏、音系复杂、声调连续变调频繁等现实瓶颈。我们联合黔东南州苗族侗族自治州语言…...
ops-math:昇腾 NPU 的数学算子库
ops-math:昇腾 NPU 的数学算子库 之前帮朋友看一个数学密集型模型(做科学计算的,不是 AI 模型)的适配代码,发现他自己手写了很多数学函数(Sin/Cos/Exp/Log 等)——在 NPU 上跑,性能只…...
从B73到5000个RILs:手把手拆解玉米NAM群体构建的完整流程与关键决策
玉米NAM群体构建全流程解析:从亲本筛选到RILs优化的科学决策 站在玉米遗传研究的十字路口,我们常常面临一个核心挑战:如何在有限资源下构建既能捕获广泛遗传多样性,又能实现精准定位的群体?2009年,Buckler团…...
2026年哪个开源商城,更适合长期维护?——真正决定商城系统寿命的,从来不是“功能多少”,而是“复杂业务长期是否还能稳定演进”
很多企业第一次选开源商城系统时。 通常都会特别关注: 功能全不全插件多不多页面好不好看上线速度快不快 因为在很多人认知里: 功能越多 → 系统越成熟 于是很多企业前期选型时。 都会优先选择: 功能最多的插件最全的营销玩法最丰富的…...
给电力行业装上“地理大脑”:百度智能云图云做了一次“地址大模型”变革
“我家在老三中对面那条巷子,供电局以前的老院子旁边……”当95598客服接到这样的报修电话时,系统该如何精准定位?这并非个例。城市快速扩张、街巷小区不断新建更名,而电力系统的地址数据往往跟不上现实变化。同时,传统…...
MSP430在便携式医疗设备中的超低功耗设计与血氧心率监测实现
1. 项目概述:为什么是MSP430?在便携式医疗设备这个赛道上,选型往往是决定项目成败的第一步。当你面对血糖仪、血氧仪这类需要用户随身携带、频繁使用、且对测量精度和电池寿命有严苛要求的产品时,一颗合适的微控制器(M…...
收藏!小白程序员必看:搞定RAG知识库,解锁大模型核心技能!
文章强调知识库是RAG系统的核心,其质量直接影响智能问答效果。构建知识库并非简单处理数据,而是涉及多数据源整合、复杂格式处理、数据更新与版本管理、文档召回优化及系统架构设计等关键环节。作者指出,随着数据量增长,完善的知识…...
选RFID仓储管理系统厂家别只盯着参数!老采购教你用场景思维找到真正靠谱的供应商
很多企业在选型RFID仓储管理系统时,第一反应是翻遍全网找“RFID智能仓储管理系统厂家有哪些”,然后把七八家供应商的参数表摊在桌上逐一对比。读取速度多少、识别距离多远、支持多少标签同时读取——这些指标当然重要,但如果你的选型逻辑仅停…...
实测taotoken在不同时段api调用的响应延迟与稳定性表现
🚀 告别海外账号与网络限制!稳定直连全球优质大模型,限时半价接入中。 👉 点击领取海量免费额度 实测taotoken在不同时段api调用的响应延迟与稳定性表现 对于依赖大模型API进行开发的团队而言,服务的响应延迟与稳定性…...
