堆排序(Heap Sort)实现

2023-10-27

定义

堆排序(英语:Heapsort)是指利用堆(heap)这种数据结构所设计的一种排序算法。堆是一个近似完全二叉树的结构,并同时满足堆积的性质:即子节点的键值或索引总是小于(或者大于)它的父节点。

堆可看作是一个「完全二叉树」的结构:

记一个二叉树的深度为 h,若除第 h 层外,该二叉树的其它各层[1, h−1] 的节点数均达到最大值(满的),且第 h 层所有的节点均连续集中在最左边,那么这棵树即为一棵 完全二叉树。

根据父子节点之间的关系,堆又可大致分为两类(如下图所示):

大根堆/大顶堆:每个节点的值均大于等于其左右孩子节点的值;
小根堆/小顶堆:每个节点的值均小于等于其左右孩子节点的值。

若对堆中的节点按照层序遍历的方式进行编号,则可将其结构映射到数组中。若从 0 开始编号(如上图所示),则节点 i 的「左子节点」为2i+1,「右子节点」为2i+2,其父节点是 (i-1)/2 (注意: 0节点除外)

此时大根堆和小根堆满足以下关系:
大根堆:nums[i] >= nums[2i+1]  &  nums[i] >= nums[2i+2]
小根堆:nums[i] <= nums[2i+1]  &  nums[i] <= nums[2i+2]

同样地,若从 1 开始对堆中的节点进行编号,则节点 i的「左子节点」为2i,「右子节点」为 2i+1,此时大根堆和小根堆满足以下关系:
大根堆:nums[i] >= nums[2i]  &  nums[i] >= nums[2i+1]
小根堆:nums[i] <= nums[2i]  &  nums[i] <= nums[2i+1]

堆与排序:

对于一个待排序的包含 n个元素的数组nums,堆排序 通常包含以下几个基本步骤:

建堆:将待排序的数组初始化为大根堆(小根堆)。此时,堆顶的元素(即根节点)即为整个数组中的最大值(最小值)。
交换和调整:将堆顶元素与末尾元素进行交换,此时末尾即为最大值(最小值)。除去末尾元素后,将其他 n−1 个元素重新构造成一个大根堆(小根堆),如此便可得到原数组 n 个元素中的次大值(次小值)。
重复步骤二,直至堆中仅剩一个元素,如此便可得到一个有序序列了。
通过以上步骤我们也可以发现:

对于「升序排列」数组,需用到大根堆;
对于「降序排列」数组,则需用到小根堆。

假设有一个待排序的数组 nums=[5, 2,1, 9, 6, 8],我们可将其构造成一个二叉树,如下图所示:

下面以 nums=[5,2,1,9,6,8] 为例,讲解下如何构造大根堆,并实现其「升序排列」。

I. 构造大根堆
如何将一个完全二叉树构造成一个大顶堆?

一个很好的实现方式是从最后一个「非叶子节点」为根节点的子树出发,从右往左、从下往上进行调整操作。

需要注意的是:

        若完全二叉树从 0 开始进行编号,则第一个非叶子节点为(n-2)/2;若完全二叉树从 1 开始进行编号,则第一个非叶子节点为 n/2。
        调整针对的是以该非叶子节点为根节点的子树,即对该根节点以下的所有部分均进行调整操作。由于我们是从右往左、从下往上遍历非叶子节点的,因此当遍历到某个非叶子节点时,它的子树(不包括该节点本身)已经是堆有序的。


对于以某个非叶子节点的子树而言,其基本的调整操作包括:

1.如果该节点大于等于其左右两个子节点,则无需再对该节点进行调整,因为它的子树已经是堆有序的;
2.如果该节点小于其左右两个子节点中的较大者,则将该节点与子节点中的较大者进行交换,并从刚刚较大者的位置出发继续进行调整操作,直至堆有序。


对于nums=[5,2,1,9,6,8],其包含n=length(nums)=6 个元素,第一个非叶子节点为(n-2)/2=2,对应的基本建堆(大根堆)步骤如下:

第一个非叶子节点 2:nums[2]<nums[5],即节点 2 小于其左子节点 5(其右子节点不存在),需要调整交换两者。如下图所示:

第二个非叶子节点 1:nums[1]<nums[3] 且 nums[1]<nums[4],即节点 1 均小于其左右子节点,但其左子节点 3 更大,因此需要调整交换节点 1 与较大的子节点 3。如下图所示:

第三个非叶子节点 0:nums[0]<nums[1] 且 nums[0]<nums[2],即节点 0 均小于其左右子节点,但其左子节点 1 更大,因此需要调整交换节点 0 与较大的子节点 1。如下图所示:

然而,调整完节点 0 与 节点 1 后我们发现原子树的堆序已被打破,此时 nums[1]<nums[4],即节点 1 小于其右子节点 4,因此还需要继续对以节点 1 为根结点的子树继续进行调整,如下图:

至此,全部的调整完毕,我们也就建立起了一个大根堆 nums=[9,6,8,2,5,1]:

II. 排序
建立起一个大根堆后,便可以对数组中的元素进行排序了。总结来看,将堆顶元素与末尾元素进行交换,此时末尾即为最大值。除去末尾元素后,将其他n−1 个元素重新构造成一个大根堆,继续将堆顶元素与末尾元素进行交换,如此便可得到原数组 n 个元素中的次大值。如此反复进行交换、重建、交换、重建,便可得到一个「升序排列」的数组。

对于大根堆 nums=[9, 6, 8, 2, 5, 1],其堆排序基本步骤如下:

1.最大元素:此时堆顶元素为最大值,将其交换到末尾,如下所示:

交换完成后,除去末尾最大元素,此时需要对堆进行重建,使得剩余元素继续满足大根堆的要求。如下所示:

2.次大元素:此时堆顶元素为待排序元素中的最大值(即原数组中的次大值),将堆顶元素交换到末尾,如下所示:

交换完成后,除去末尾最大元素,此时需要对堆进行重建,使得剩余元素继续满足大根堆的要求(省略)。

3.第三大元素:此时堆顶元素为待排序元素中的最大值(即原数组中的第三大值),将堆顶元素交换到末尾,如下所示:

交换完成后,除去末尾最大元素,此时需要对堆进行重建,使得剩余元素继续满足大根堆的要求(省略)。

4.第四大元素:此时堆顶元素为待排序元素中的最大值(即原数组中的第四大值),将堆顶元素交换到末尾,如下所示:

交换完成后,除去末尾最大元素,此时需要对堆进行重建,使得剩余元素继续满足大根堆的要求(省略)。

5.次小元素(第五大元素):此时堆顶元素为待排序元素中的最大值(即原数组中的次小元素或第五大元素),将堆顶元素交换到末尾,如下所示:

交换完成后,除去末尾最大元素,此时堆中仅剩一个元素,即为原数组中的最小值。

至此,基于大根堆的升序排列完成,如下所示:

本题分析:

注意到,在每一次建好大根堆后,堆顶元素即为当前范围内的最大值,因此要得到数组中的第 K 个最大元素需要:建堆(大根堆) K 次,此时堆顶元素即为第 K 个最大元素。

堆排序(大根堆)

public class HeapSort {
    @Test
    public void test() {
        int[] arr = {5, 2, 1, 9, 6, 8};
        heapSort(arr);
    }

    public void heapSort(int[] arr) {
        //从第一个非叶子节点进行调整
        int startIndex = (arr.length - 2) / 2;
        for (int i = startIndex; i >= 0; i--) {
            toMaxHeap(arr, arr.length, i);
        }
        //已经变成大根堆,交换堆顶和最后一个元素,循环
        for (int i = arr.length - 1; i > 0; i--) {
            swap(arr, i, 0);
            //size=i,一直在减小
            toMaxHeap(arr, i, 0);
        }
        System.out.println(Arrays.toString(arr));
    }

    //构造大根堆
    public void toMaxHeap(int[] arr, int size, int index) {
        int leftChildIndex = 2 * index + 1;
        int rightChildIndex = 2 * index + 2;
        int maxIndex = index;
        //找到最大的元素
        if (leftChildIndex < size && arr[leftChildIndex] > arr[maxIndex]) {
            maxIndex = leftChildIndex;
        }
        if (rightChildIndex < size && arr[rightChildIndex] > arr[maxIndex]) {
            maxIndex = rightChildIndex;
        }
        //如果是子节点比父节点大,则进行交换
        if (maxIndex != index) {
            swap(arr, index, maxIndex);
        //重新调整大根堆顺序
            toMaxHeap(arr, size, maxIndex);
        }
    }

    public void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}

小根堆排序

//可以直接用 PriorityQueue 优先队列实现
public class findKthLargest {
    @Test
    public void test(){
        int[] arr = {56,275,12,6,45,478,41,1236,456,12,546,45};
        int[] largest = KthLargest(arr, 5);
        System.out.println(Arrays.toString(largest));
    }


    public int[] KthLargest(int[] arr,int k){
        //1.取前k个元素先组成topk数组
        int[] topK = new int[k];
        for (int i = 0; i < k; i++) {
            topK[i] = arr[i];
        }
        //2.构造小根堆,从第一个非叶子节点进行调整
        int startIndex = (k - 2) / 2;
        for (int i = startIndex; i >= 0; i--) {
            toMinHeap(topK, k, i);
        }

        //3.从k开始遍历数组,比较大小,是否替换元素
        //小根堆第一个元素最小,替换掉最小的
        for (int i = k - 1; i < arr.length; i++) {
            if(arr[i] > topK[0]){
                topK[0] = arr[i];
                // 重新调整结构为小根堆
                toMinHeap(topK, k, 0);
            }
        }
        return topK;
    }

    public void toMinHeap(int[] arr,int size,int index){
        int leftChild = index * 2 + 1;
        int rightChild = index * 2 + 2;
        int minIndex = index;
        if(leftChild < size && arr[leftChild]<arr[minIndex]){
            minIndex = leftChild;
        }
        if(rightChild < size && arr[rightChild]<arr[minIndex]){
            minIndex = rightChild;
        }
        if(minIndex!=index){
            swap(arr,index,minIndex);
            toMinHeap(arr,size,minIndex);
        }
    }

    public void swap(int[] arr,int i,int j){
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

堆排序(Heap Sort)实现 的相关文章

  • java.lang.NoClassDefFoundError:org.apache.batik.dom.svg.SVGDOMImplementation

    我在链接到我的 Android LibGDX 项目的 Apache Batik 库时遇到了奇怪的问题 但让我们从头开始 在 IntelliJ Idea 中我有一个项目 其中包含三个模块 Main Android 和 Desktop 我强调的
  • 在 Java 中连接和使用 Cassandra

    我已经阅读了一些关于 Cassandra 是什么以及它可以做什么的教程 但我的问题是如何在 Java 中与 Cassandra 交互 教程会很好 如果可能的话 有人可以告诉我是否应该使用 Thrift 还是 Hector 哪一个更好以及为什
  • Java Swing:从 JOptionPane 获取文本值

    我想创建一个用于 POS 系统的新窗口 用户输入的是客户拥有的金额 并且窗口必须显示兑换金额 我是新来的JOptionPane功能 我一直在使用JAVAFX并且它是不同的 这是我的代码 public static void main Str
  • 如何默认将 Maven 插件附加到阶段?

    我有一个 Maven 插件应该在编译阶段运行 所以在项目中consumes我的插件 我必须做这样的事情
  • Java EE:如何获取我的应用程序的 URL?

    在 Java EE 中 如何动态检索应用程序的完整 URL 例如 如果 URL 是 localhost 8080 myapplication 我想要一个可以简单地将其作为字符串或其他形式返回给我的方法 我正在运行 GlassFish 作为应
  • 给定两个 SSH2 密钥,我如何检查它们是否属于 Java 中的同一密钥对?

    我正在尝试找到一种方法来验证两个 SSH2 密钥 一个私有密钥和一个公共密钥 是否属于同一密钥对 我用过JSch http www jcraft com jsch 用于加载和解析私钥 更新 可以显示如何从私钥 SSH2 RSA 重新生成公钥
  • 使用 Android 发送 HTTP Post 请求

    我一直在尝试从 SO 和其他网站上的大量示例中学习 但我无法弄清楚为什么我编写的示例不起作用 我正在构建一个小型概念验证应用程序 它可以识别语音并将其 文本 作为 POST 请求发送到 node js 服务器 我已确认语音识别有效 并且服务
  • 在 HTTPResponse Android 中跟踪重定向

    我需要遵循 HTTPost 给我的重定向 当我发出 HTTP post 并尝试读取响应时 我得到重定向页面 html 我怎样才能解决这个问题 代码 public void parseDoc final HttpParams params n
  • 加速代码 - 3D 数组

    我正在尝试提高我编写的一些代码的速度 我想知道从 3d 整数数组访问数据的效率如何 我有一个数组 int cube new int 10 10 10 我用价值观填充其中 然后我访问这些值数千次 我想知道 由于理论上所有 3d 数组都存储在内
  • 列出jshell中所有活动的方法

    是否有任何命令可以打印当前 jshell 会话中所有新创建的方法 类似的东西 list但仅适用于方法 您正在寻找命令 methods all 它会打印所有方法 包括启动 JShell 时添加的方法 以及失败 被覆盖或删除的方法 对于您声明的
  • Liferay ClassNotFoundException:DLFileEntryImpl

    在我的 6 1 0 Portal 实例上 带有使用 ServiceBuilder 和 DL Api 的 6 1 0 SDK Portlet 这一行 DynamicQuery query DynamicQueryFactoryUtil for
  • 操作错误不会显示在 JSP 上

    我尝试在 Action 类中添加操作错误并将其打印在 JSP 页面上 当发生异常时 它将进入 catch 块并在控制台中打印 插入异常时出错 请联系管理员 在 catch 块中 我添加了它addActionError 我尝试在jsp页面中打
  • Spring @RequestMapping 带有可选参数

    我的控制器在请求映射中存在可选参数的问题 请查看下面的控制器 GetMapping produces MediaType APPLICATION JSON VALUE public ResponseEntity
  • 禁止的软件包名称:java

    我尝试从数据库名称为 jaane 用户名 Hello 和密码 hello 获取数据 错误 java lang SecurityException Prohibited package name java at java lang Class
  • 总是使用 Final?

    我读过 将某些东西做成最终的 然后在循环中使用它会带来更好的性能 但这对一切都有好处吗 我有很多地方没有循环 但我将 Final 添加到局部变量中 它会使速度变慢还是仍然很好 还有一些地方我有一个全局变量final 例如android Pa
  • 如何从泛型类调用静态方法?

    我有一个包含静态创建方法的类 public class TestClass public static
  • 玩!框架:运行“h2-browser”可以运行,但网页不可用

    当我运行命令时activator h2 browser它会使用以下 url 打开浏览器 192 168 1 17 8082 但我得到 使用 Chrome 此网页无法使用 奇怪的是它以前确实有效 从那时起我唯一改变的是JAVA OPTS以启用
  • Firebase 添加新节点

    如何将这些节点放入用户节点中 并创建另一个节点来存储帖子 我的数据库参考 databaseReference child user getUid setValue userInformations 您需要使用以下代码 databaseRef
  • JGit 检查分支是否已签出

    我正在使用 JGit 开发一个项目 我设法删除了一个分支 但我还想检查该分支是否已签出 我发现了一个变量CheckoutCommand但它是私有的 private boolean isCheckoutIndex return startCo
  • 使用 xpath 和 vtd-xml 以字符串形式获取元素的子节点和文本

    这是我的 XML 的一部分

随机推荐

  • I - Intense Bit Wheel (二进制,bitset)

    SDUT 2022 Summer Individual Contest 2 for 21 Virtual Judge There is a new intense giant wheel in UNAL town in UNAL town
  • uniapp整包apk更新下载后安装完删除下载的apk,避免占用app内存

    一 内存大 app版本更新后内存变的好大 更新多几次版本更大 小编便发现是每次更新后都把下载下来的apk大小增加到app里面去了 那么我们如何版本更新完之后删除下载下来的apk文件呢 避免占用内存 二 解决方案 使用html5 功能IO模块
  • NLP预训练模型-GPT-3

    NLP预训练模型 系列文章 1 BERT 2 GPT 3 GPT 2 4 GPT 3 目录 NLP预训练模型系列文章 文章目录 前言 1 Abstract 2 Introduction 3 Approach 3 1 模型和架构 3 2 训练
  • oracle11g 导出表报EXP-00011:table不存在。

    转自 https blog csdn net mingzaiwang article details 52608991 depth 1 utm source distribute pc relevant none task utm sour
  • 8种提升程序猿编程能力的方法+编程思维四个核心:分解、抽象、模式识别和算法

    8种提升程序猿编程能力的方法 对于程序员来说 提高自己的编程能力 算是给自己定的职业发展目标之一 不过定一个成为编程大神的目标很容易 具体做起来可能就不是一件简单的事了 首先 既然决定 我要变得更好 得先知道 更好 是什么样子的 另外 不能
  • C++ 单链表节点交换

    这里提供两种方法 一种是只交换对应的数据 另一种是通过更改指针来交换节点 而更改指针中又可以分为新建节点与不新建节点的方法 1 不更改指针 这个没啥好说的 直接将对应的data交换即可 这里的a c节点都为被交换节点的上一个节点 因为不更改
  • el-form之表单校验自动定位到报错位置

    1 背景 表单校验大多数的表单都会用到 一般情况下只是提示当前哪些项校验不通过 但是如果表单比较需要用户自己去找是哪项校验不通过 这样的用户体验不太好 如果能自动定位到当前校验不通过的表单项体验会更好一些 这里是以elementui 的 e
  • 主线剧情-番外02-设备树详解

    设备树详解 本文 续接 主线剧情03 NXP i MX 系列 u boot 移植基础详解 一文中移植过程小节中有关设备树的内容 编辑整理 By Staok 如有错误恭谢指出 侵删 CC BY NC SA 4 0 注意 本文适合学习设备树的一
  • Spring参数校验和全局异常处理

    目录 一 前言 二 Validation 1 JSR 303 2 Spring Validation 3 Validated和 Valid的区别 三 全局异常处理 1 为何要处理异常 2 RestControllerAdvice 3 返回自
  • Vue项目启动报错:error:cannot find module xxx

    原因 无法找到项目依赖的某个模块 解决办法 1 删掉存放模块的文件夹node module 2 执行清除缓存命令 npm cache clean 如果报错 使用强制清除npm cache clean force 如果还报错 删除packag
  • Unity滑入Button/按键/UI范围检测

    效果展示 鼠标滑入按键的点击范围后 对应的游戏背景会发生改变 将下面的脚本挂在需要检测的UI上即可 记得引用必要的操作 using System Collections using System Collections Generic us
  • 接口测试初认知

    接口测试初认知 一 概念 根据分层自动化测试中的定义 最底层由开发人员编写的单元测试保证代码质量 最上层由功能测试人员手工 UI自动化进行大量的自动化功能测试保证功能的可用 则中间层的接口测试是什么作用呢 接下里我们就学习接口测试 那说到接
  • matplotlib如何降低x轴密度-时间显示问题

    python中轴数据的 稀释 在python中很多时候都会遇到x轴数据过多而显示出问题的问题 因此这篇文章针对于时间显示问题来做出解答 可以到到原始数据是 可以先简单绘制数据图 代码如下 绘制结果图如下 结果无法显示出日期 因此需要导入ti
  • C++压缩解压开源库ZIP

    1 ZIP下载 ZIP 主要是用于简单的压缩和解压 引入比较方便 而且极其易使用 方便用户操作 下载地址 http www codeproject com Articles 7530 Zip Utils clean elegant simp
  • 软连接文件的创建删除

    demo 通过系统命令 实现一个对视频文件可以操作的软连接路径 在Nginx中的HTML页面可直接访问视频文件 目录的创建 文件的链接 文件的删除 include
  • DA14585-我手上的开发板(串口2)

    名字 DA14585 Development Kit Pro DA14585 Development Kit Pro Dialog视频 DA14585 Development Kit Pro Dialog 文件结构参考 DA14585 SD
  • 百度OCR文字识别及使用案例

    百度OCR文字识别使用案例 案例环境 Windows10 Jdk1 8 IDEA2019 3 5旗舰版 一 账号注册及创建应用 1 访问地址 https ai baidu com tech ocr general track cp aipi
  • —————数组循环之终极宝典

    一 数组循环的方法 for forEach map for of filter every find 1 for循环 最基础的循环方式 速度较快 效率较高 而且可以控制数组的任意一项元素 const arr 1 2 3 4 5 for le
  • Java并发编程:线程池的使用

    https www cnblogs com dolphin0520 p 3932921 html Java并发编程 线程池的使用 在前面的文章中 我们使用线程的时候就去创建一个线程 这样实现起来非常简便 但是就会有一个问题 如果并发的线程数
  • 堆排序(Heap Sort)实现

    定义 堆排序 英语 Heapsort 是指利用堆 heap 这种数据结构所设计的一种排序算法 堆是一个近似完全二叉树的结构 并同时满足堆积的性质 即子节点的键值或索引总是小于 或者大于 它的父节点 堆可看作是一个 完全二叉树 的结构 记一个