常见排序算法总结 (五) - 堆排序与堆操作

堆排序(借助 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 + 1
    int 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 + 1
    int 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 + 1
    int 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. 排序数组 进行测试,堆排序能够比较高效地完成任务,大致与随机快速排序相当。

相关阅读

  • 常见排序算法总结 (一) - 三种基本排序
  • 常见排序算法总结 (二) - 不基于比较的排序
  • 常见排序算法总结 (三) - 归并排序与归并分治
  • 常见排序算法总结 (四) - 快速排序与随机选择

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

visual studio添加滚动条预览

如何在vs中添加如图的滚动条呢&#xff1f; 打开VS 工具栏 选项 - 文本编辑器 - C/C - 滚动条 行为-使用缩略图 -- 确定

VUE利用一句话复刻实现变声功能

实现思路&#xff1a;利用语音听写实现语音输入---拿到文字后自动调用一句话复刻实现声音输出。最终效果是A输入语音能够转换成B的语音输出。 <template><div class"One-container"><div><hr/><!--发音音色列表展示--><el-row :gut…

Firefly: 大模型训练工具,命令行执行训练,没有界面

文章目录 GitHub地址参数说明训练命令 Firefly: 大模型训练工具&#xff0c;支持训练Qwen2.5、Qwen2、Yi1.5、Phi-3、Llama3、Gemma、MiniCPM、Yi、Deepseek、Orion、Xverse、Mixtral-8x7B、Zephyr、Mistral、Baichuan2、Llma2、Llama、Qwen、Baichuan、ChatGLM2、InternLM、Zi…

自动驾驶AVM环视算法--python版本的俯视碗型投影模式

c语言版本和算法原理的可以查看本人的其他文档。《自动驾驶AVM环视算法--3D碗型投影模式的exe测试工具》本文档进用于展示部分代码的视线&#xff0c;获取方式网盘自行获取&#xff08;非免费介意勿下载&#xff09;&#xff1a;链接: https://pan.baidu.com/s/1STjUd87_5wDk_C…

汽车供应链 “剧变”开始,“智能感知潜在龙头”诞生

智能汽车产业链“剧变”已经开启&#xff0c;智能感知软硬件能力的权重正在不断被放大。 比如满足高阶泊车的第二代AK2超声波传感器、满足人机共驾场景需求的电子外后视镜&#xff08;CMS&#xff09;、iTOF 3D成像视觉感知&#xff08;用于舱内监控&#xff09;等新产品&…

计算机网络——期末复习(2)1-3章考试重点

第一章 概述 1、发送时延&#xff0c;其中数据帧长度为数据块大小1B8位 2、传播时延 3、 第二章 物理层 1、奈氏准则&#xff1a;理想低通信道的最高码元传输速率2W&#xff0c;W为理想低通信道的频率带宽 2、香农公式&#xff1a;&#xff0c;C为信道的极限信息传输速率&…

C++算法第九天

本篇文章我们继续学习c算法 目录 第一题 题目链接 题目展示 代码原理 暴力解法 二分解法 代码编写 第二题 题目链接 题目展示 代码原理 代码编写 重点回顾 朴素二分 非朴素二分 重点一 重点二 重点三 第一题 题目链接 153. 寻找旋转排序数组中的最小值 - 力…

HarmonyOS 实时监听与获取 Wi-Fi 信息

文章目录 摘要项目功能概述代码模块详细说明创建 Wi-Fi 状态保存对象Wi-Fi 状态监听模块获取当前 Wi-Fi 信息整合主模块 运行效果展示性能分析总结 摘要 本文展示了如何使用 HarmonyOS 框架开发一个 Demo&#xff0c;用于监听手机的 Wi-Fi 状态变化并实时获取连接的 Wi-Fi 信息…

gpu硬件架构

1.简介 NVIDIA在视觉计算和人工智能&#xff08;AI&#xff09;领域处于领先地位&#xff1b;其旗舰GPU已成为解决包括高性能计算和人工智能在内的各个领域复杂计算挑战所不可或缺的。虽然它们的规格经常被讨论&#xff0c;但很难掌握各种组件的清晰完整的图景。 这些GPU的高性…

【Qt】显示类控件:QLabel、QLCDNumber、QProgressBar、QCalendarWidget

目录 QLabel QFrame 例子&#xff1a; textFormat pixmap、scaledContents alignment wordWrap、indent、margin buddy QLCDNumber 例子&#xff1a; QTimer QProgressBar 例子&#xff1a; QCalendarWidget 例子&#xff1a; QLabel 标签控件&#xff0c;用来显示…

0001.基于springmvc简易酒店管理系统后台

一.系统架构 springmvcjsplayuimysql 二.功能特性 简单易学习&#xff0c;虽然版本比较老但是部署方便&#xff0c;tomcat环境即可启用&#xff1b;代码简洁&#xff0c;前后端代码提供可统一学习&#xff1b;祝愿您能成尽快为一位合格的程序员&#xff0c;愿世界没有BUG; …

Wallpaper壁纸制作学习记录12

角色表 创建人偶变形动画的更高级方法可以使用角色表来完成。角色表要求您使用角色的切割版本&#xff0c;将您的角色分成不同肢体/部分。这允许创建更复杂、更准确的动画&#xff0c;因为部分可以自由移动和重叠&#xff0c;而不会使图像失真。使用操控变形不一定能获得良好的…

【Python项目】基于Django的语音和背景音乐分离系统

【Python项目】基于Django的语音和背景音乐分离系统 技术简介&#xff1a;采用Python技术、Django框架、B/S结构&#xff0c;MYSQL数据库等实现。 系统简介&#xff1a;系统完成在线的音频上传&#xff0c;并且通过计算机的神经网络算法来对系统中的背景音乐和人声进行分离操作…

负载均衡oj项目:介绍

目录 项目介绍 项目演示 项目介绍 负载均衡oj是一个基于bs模式的项目。 用户使用浏览器向oj模块提交代码&#xff0c;oj模块会在所有在线的后端主机中选择一个负载情况最低的主机&#xff0c;将用户的代码提交给该主机&#xff0c;该主机进行编译运行&#xff0c;将结果返回…

【鸿睿创智开发板试用】移植OpenCV 4到OpenHarmony 4.1

目录 目录 引言 编译系统镜像 (1) 下载代码后解压SDK (2) 下载docker镜像   (3) 编译OH 编译OpenCV 下载OpenCV源代码 构建编译配置文件 执行编译命令 安装库和头文件 测试 结语 引言 最近有个需求是在基于RK3568的OpenHarmony 4.1系统中使用OpenCV&#xff0c…

【HarmonyOS之旅】HarmonyOS开发基础知识(一)

目录 1 -> 应用基础知识 1.1 -> 用户应用程序 1.2 -> 用户应用程序包结构 1.3 -> Ability 1.4 -> 库文件 1.5 -> 资源文件 1.6 -> 配置文件 1.7 -> pack.info 1.8 -> HAR 2 -> 配置文件简介 2.1 -> 配置文件的组成 3 -> 配置文…

【机器人】Graspness 端到端抓取点估计 | 环境搭建 | 模型推理测试

在复杂场景中实现抓取检测&#xff0c;Graspness是一种端到端的方法&#xff1b; 输入点云数据&#xff0c;输出抓取角度、抓取深度、夹具宽度等信息。 开源地址&#xff1a;https://github.com/rhett-chen/graspness_implementation?tabreadme-ov-file 论文地址&#xff1…

B站bilibili视频转文字字幕下载方法

本文将讲述介绍一种使用本地工具如何快速的下载B站的字幕为本地文本文件的方法。 通常获取B站字幕需要在浏览器中安装第三方插件&#xff0c;通过插件获取字幕。随着大模型&#xff0c;生成式AI&#xff0c;ChatGPT的应用&#xff0c;B站也提供了AI小助手对视频的内容进行总结…

CSS3 实现火焰-小火苗效果

创建 CSS3 火焰效果可以通过组合 CSS 动画、伪元素 和 渐变 来实现。以下是一个简单的实现步骤&#xff0c;展示如何制作动态火焰效果 1. HTML 结构 我们只需要一个简单的 div 容器&#xff1a; <div class"fire"></div>2. CSS 实现 基础样式 使用 …

新能源汽车充电需求攀升,智慧移动充电服务有哪些实际应用场景?

在新能源汽车行业迅猛发展的今天&#xff0c;智慧充电桩作为支持这一变革的关键基础设施&#xff0c;正在多个实际应用场景中发挥着重要作用。从公共停车场到高速公路服务区&#xff0c;从企业园区到住宅小区&#xff0c;智慧充电桩不仅提供了便捷的充电服务&#xff0c;还通过…