最难以理解的排序算法 - 堆排序(超详解)

2023-11-14

堆排序基本介绍

  1. 堆排序是利用堆这种数据结构而设计的一种排序算法,堆排序是一种选择排序,它的最坏,最好,平均时间复杂度均为O(nlogn),它也是不稳定排序。
  2. 要理解堆排序,必须先要理解堆这种数据结构

    堆是具有以下性质的完全二叉树:

    1. 每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆, 注意 : 没有要求结点的左孩子的值和右孩子的值的大小关系。
    2. 每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。
  3. 大顶堆示意图:
    在这里插入图片描述
    我们对堆中的结点按层进行编号,映射到数组中就是下面这个样子:
    在这里插入图片描述
    大顶堆特点:arr[i] >= arr[2*i+1] && arr[i] >= arr[2*i+2] // i 对应第几个节点,i从0开始编号
    注意:这里需要先理解顺序存储二叉树的知识,可以参考我的文章顺序存储二叉树
  4. 小顶堆示意图:
    5.
    小顶堆:arr[i] <= arr[2*i+1] && arr[i] <= arr[2*i+2] // i 对应第几个节点,i从0开始编号
  5. 一般升序采用大顶堆,降序采用小顶堆

堆排序基本思想

堆排序的基本思想是:
  1. 将待排序序列构造成一个大顶堆
  2. 此时,整个序列的最大值就是堆顶的根节点。
  3. 将其与末尾元素进行交换,此时末尾就为最大值。
  4. 然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列了。

可以看到在构建大顶堆的过程中,元素的个数逐渐减少,最后就得到一个有序序列了

堆排序思路和步骤:

步骤一 构造初始堆。将给定无序序列构造成一个大顶堆(一般升序采用大顶堆,降序采用小顶堆)

  1. 假设给定无序序列结构如下
    在这里插入图片描述

  2. 此时我们从数组的最后一个非叶子结点(即下标为arr.length/2-1)开始(叶结点自然不用调整,最后一个非叶子结点 arr.length/2-1=5/2-1=1,也就是下面的6结点),从右至左,从下至上进行调整。
    在这里插入图片描述

  3. 找到第二个非叶节点4,由于[4,9,8]中9元素最大,4和9交换。
    在这里插入图片描述

  4. 这时,交换导致了子根[4,5,6]结构混乱,继续调整,[4,5,6]中6最大,交换4和6。
    在这里插入图片描述

    此时,我们就将一个无序序列构造成了一个大顶堆。

步骤二 将堆顶元素与末尾元素进行交换,使末尾元素最大。然后继续调整堆,再将堆顶元素与末尾元素交换,得到第二大元素。如此反复进行交换、重建、交换。

  1. 将堆顶元素9和末尾元素4进行交换
    在这里插入图片描述

  2. 重新调整结构,使其继续满足堆定义

在这里插入图片描述

  1. 再将堆顶元素8与末尾元素5进行交换,得到第二大元素8
    在这里插入图片描述
  2. 后续过程,继续进行调整,交换,如此反复进行,最终使得整个序列有序
    在这里插入图片描述

堆排序的代码实现

堆排序的代码不是很好理解,代码里面有详细注释,可以仔细阅读代码注释加深对堆排序理解

public class HeapSort {
    public static void main(String[] args) {
		int[] array = new int[80000];
        for (int i = 0; i < array.length; i++) {
            // 随机生成一个0到8000000的随机数
            Random random = new Random();
            int nextInt = random.nextInt(8000000);
            array[i] = nextInt;
        }
        // 排序前时间,h毫秒
        long beforeSortTimeMillis = System.currentTimeMillis();
        heapSort(array);
        // 排序后时间
        long afterSortTimeMillis = System.currentTimeMillis();
        System.out.println("排序80000个数据总共花费时间为:" + (afterSortTimeMillis - beforeSortTimeMillis) + "毫秒");
        
       int[] arr = {4,6,8,5,9,74,1,45,23,46,26,26}; // 调整成大顶堆为 9,6,8,5,4
       heapSort(arr);
       System.out.println(Arrays.toString(arr));
    }

    /**
     * 按堆排序,把数组变成一个升序的数组
     * @param array
     */
    public static void heapSort(int[] array) {

        // 把该数组看成一个顺序存储的二叉树,升序排序,先把该数组调整成一个大顶堆,
        // 调整成大顶堆,先从该顺序二叉树的最后非叶子节点开始调整,从右到左,从下到上
        // 最后一个非叶子节点的 下标为 array.length/2-1
        for (int i = array.length/2-1; i >= 0; i--) {
            adjustHeap(array,i,array.length);
        }

        // 代码走到这该数组已经是一个大顶堆,头结点是最大值,即数组的第一个元素是最大值
        // 把该数组的头节点与末尾交换,然后把除去数组末尾的数继续调整成大顶堆
        for (int n = array.length - 1; n > 0 ; n--) {
            int temp = array[0];
            array[0] = array[n];
            array[n] = temp;

            // 然后把该数组的从下标为0开始,长度为n的数组继续调整成一个大顶堆
            // 即以数组的第一个元素为根节点开始调整,
            // 因为以array[0]根节点的树,该树下面的每一颗子树都已经是大顶堆了
            // 参考到adjustHeap()方法的作用,所以可以直接调用该方法,
            // 把array[0]调整到该树合适的位置,让该树继续是一个大顶堆
            adjustHeap(array,0,n);
        }
    }

    /**
     * 该方法总的作用是把以array[i]为根节点的树的头节点array[i]按大小调整到其合适的位置,从上往下,逐层比较,最后找打array[i]合适的位置
     * 1.该方法把以下标为i的数作为根节点的树调整成一个大顶堆,
     * 2.要满足1,必须先把以arrry[i]为根节点的树下面的每一颗树都必须先调整成一个大顶堆,
     * 3.也就是想要把以array[i]为根节点的数调整成大顶堆,必须要循环调用该方法,
     * 先从该树的最后一个非叶子节点开始递减调整,才能把该树调整成一个大顶堆
     * @param array 看做一个顺序存储的二叉树的数组
     * @param i 以i为根节点数
     * @param length 要调整的数组长度
     */
    public static void adjustHeap(int[] array, int i, int length) {
        // 先保存该根节点
        int temp = array[i];
        // 左子节点 i*2+1   右子节点 i*2+2

        // 循环遍历,使k指向左子节点,每次循环在指向下一个左子节点
        // 该循环中右2个变量需要理解,i是父节点,k是其子节点,注意i和k的变化,可以理解成指针
        for (int k = i*2+1; k < length; k=k*2+1) {
            // 比较左右节点的大小,如果右节点大于左节点就让k指向右节点
            if (k+1 < length && array[k+1] > array[k]) {
                k++;
            }
            // 在比较初始根节点的值(即保存在temp的值)和array[k](即左右节点中较大的那个值)的大小
            if (array[k] > temp) {
                // 如果大于则把该左右节点较大的值赋值给其父节点
                array[i] = array[k];
                // 让i指向k,即让i移动到左右节点中较大那个节点,然后继续循环下一次
                i=k;
            } else {
                // 代码走到这说明,该左右子节点的较大值不大于该树的原始根节点(即temp,)
                // 也就不用继续比较下去了,头节点已经找到其合适的位置,循环终止退出
                break;
            }
        }
        // 代码执行到这,说明已经找到合适的位置即下标为i的位置,最后把头节点的值放到到他合适的位置
        array[i] = temp;
    }
}

运行结果:

排序80000个数据总共花费时间为:8毫秒
[1, 4, 5, 6, 8, 9, 23, 26, 26, 45, 46, 74]

从结果可以看出堆排序是非常快的,排序80000个数据才用了8毫秒,堆排序的事件复杂度是O(nlogn)

对以上代码再次说明:

  1. 以上代码中要理解方法adjustHeap()方法的作用,该方法并不是把传入的一个数组变成大顶堆,而是结合heapSort()方法中的代码:
    for (int i = array.length/2-1; i >= 0; i--) {
                adjustHeap(array,i,array.length);
            }
    
    当这段for循环结束,才会把数组变成一个大顶堆
  2. 然后heapSort()方法下的代码:
    for (int n = array.length - 1; n > 0 ; n--) {
                int temp = array[0];
                array[0] = array[n];
                array[n] = temp;
    
                // 然后把该数组的从下标为0开始,长度为n的数组继续调整成一个大顶堆
                // 即以数组的第一个元素为根节点开始调整,
                // 因为以array[0]根节点的树,该树下面的每一颗子树都已经是大顶堆了
                // 参考到adjustHeap()方法的作用,所以可以直接调用该方法,
                // 把array[0]调整到该树合适的位置,让该树继续是一个大顶堆
                adjustHeap(array,0,n);
            }
    
    此代码的作用是把数组头元素和尾元素进行交换,然后继续调用adjustHeap()把头元素调整到合适位置,让除去已找到最大值的数组继续是一个大顶堆,在交换如此反复执行,直到数组是一个有序数组。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

最难以理解的排序算法 - 堆排序(超详解) 的相关文章

  • 《Linux From Scratch》第三部分:构建LFS系统 第六章:安装基本的系统软件- 6.29. Coreutils-8.23...

    Coreutils 软件包包含用于显示和设置基本系统特性的工具 大概编译时间 2 5 SBU 需要磁盘空间 193 MB 6 29 1 安装 Coreutils POSIX 要求 Coreutils 中的程序即使在多字节语言环境也能正确识别
  • 将二叉树转为有序的双向链表

    一 题目要求 输入一棵二叉排序树 现在要将该二叉排序树转换成一个有序的双向链表 而且在转换的过程中 不能创建任何新的结点 只能调整树中的结点指针的指向来实现 include
  • 直线检测方法—LSD论文翻译

    附原文链接 LSD a Line Segment Detector 摘 要 LSD是一个线段检测器 能够在线性时间内得到亚像素级精度的检测结果 它无需调试参数就可以适用于任何数字图像上 并且能够自我控制错误数量的检测 平均来说 一个图像中允
  • 微软2013暑假实习生笔试题

    自己mark一下 以作后备 下面提交原文链接 原文博客 部分题目答案不确定 会持续更新 1 Which of the following calling convention s support s supportvariable leng
  • 逆波兰表达式求值(C语言实现)

    实验项目 从文本文件输入任意一个语法正确的 中缀 表达式 显示并保存该表达式 利用栈结构 把上述 中缀 表达式转换成后缀表达式 并显示栈的状态变化过程和所得到的后缀表达式 利用栈结构 对上述后缀表达式进行求值 并显示栈的状态变化过程和最终结
  • 递归算法中的时间复杂度分析

    对于一种算法的时间复杂度分析还是特别重要的 在一些非递归算法中 我们仅仅看运算次数最多的那一行代码可能执行多少次就可以 实际就是看在循环中变量的变化 但是对于递归算法中该怎么分析呢 下面介绍几种递归函数中的算法时间复杂度分析的方法 0 递推
  • 亚利桑那州立大学周纵苇:研习 U-Net ——现有的分割网络创新

    雷锋网AI研习社按 经典的 Encoder Decoder 结构在目标分割问题中展现出了举足轻重的作用 然而这样一个相对固定的框架使得模型在感受野大小和边界分割精度两方面很难达到兼顾 本次公开课 讲者以 U Net 为案例分析 总结现有的分
  • Python 实现列队

    1 列队定义 队列是项的有序结合 其中添加新项的一端称为队尾 移除项的一端称为队首 当一个元素从队尾进入队列时 一直向队首移动 直到它成为下一个需要移除的元素为止 最近添加的元素必须在队尾等待 集合中存活时间最长的元素在队首 这种排序成为
  • 数据结构之图的两种遍历实现(C语言版)

    上一期文章分享完了图的两种遍历方式 也是两种很重要的算法 DFS和BFS 这两种算法的应用和重要性我就不多说了 内行的人懂的都懂 今天这文章重要就是来上机实现这两种算法 又由于这两种算法都可以由邻接矩阵和邻接表来表示 博主分享的代码都是上机
  • 以太坊系列之十五: 以太坊数据库

    以太坊数据库中都存了什么 以太坊使用的数据库是一个NOSQL数据库 是谷歌提供的开源数据leveldb 这里尝试通过分析以太坊数据库存储了什么来分析以太坊可能为我们提供哪些关于区块链的API 存储内容 NOSQL是一个key value数据
  • CRC校验(二)

    CRC校验 二 参考 https blog csdn net liyuanbhu article details 7882789 https www cnblogs com esestt archive 2007 08 09 848856
  • 数理统计知识整理——回归分析与方差分析

    题记 时值我的北科研究生第一年下 选学 统计优化 课程 备考促学 成此笔记 以谨记 1 线性回归 1 1 原理分析 要研究最大积雪深度x与灌溉面积y之间的关系 测试得到近10年的数据如下表 使用线性回归的方法可以估计x与y之间的线性关系 线
  • 区块链中的哈希算法

    区块链中的密码学 密码学在区块链中的应用主要有两个 哈希算法与非对称加密算法 这次主要对哈希算法进行详细的说明 哈希算法 哈希算法的特点有 1 输入可以为任意大小的字符串 2 产生固定大小的输出 3 可以在合理的时间内算出输出值 若要满足密
  • 索引优化之Explain 及慢查询日志

    索引 本质是数据结构 简单理解为 排好序的快速查找数据结构 以索引文件的形式存储在磁盘中 目的 提高数据查询的效率 优化查询性能 就像书的目录一样 优势 提高检索效率 降低IO成本 排好序的表 降低CPU的消耗劣势 索引实际也是一张表 该表
  • Leetcode1094. 拼车

    Every day a Leetcode 题目来源 1094 拼车 解法1 差分数组 对于本题 设 a i 表示车行驶到位置 i 时车上的人数 我们需要判断是否所有 a i 都不超过 capacity trips i 相当于把 a 中下标从
  • 牛客剑指offer刷题其他算法篇

    文章目录 构建乘积数组 题目 思路 代码实现 第一个只出现一次的字符
  • 【数据结构】单链表的定义和操作

    目录 1 单链表的定义 2 单链表的创建和初始化 3 单链表的插入节点操作 4 单链表的删除节点操作 5 单链表的查找节点操作 6 单链表的更新节点操作 7 完整代码 嗨 我是 Filotimo 很高兴与大家相识 希望我的博客能对你有所帮助
  • 按照层次遍历结果打印完全二叉树

    按照层次遍历结果打印完全二叉树 按照推论结果 l 层首个节点位置 2 h l 1 l 层节点间距 2 h l 1 1 编码实现 public static
  • 浅谈归并排序:合并 K 个升序链表的归并解法

    在面试中遇到了这道题 如何实现多个升序链表的合并 这是 LeetCode 上的一道原题 题目具体如下 用归并实现合并 K 个升序链表 LeetCode 23 合并K个升序链表 给你一个链表数组 每个链表都已经按升序排列 请你将所有链表合并到
  • 高精度运算合集,加减乘除,快速幂,详细代码,OJ链接

    文章目录 零 前言 一 加法 高精度加法步骤 P1601 A B 二 减法 高精度减法步骤

随机推荐

  • 算法——因子和阶乘

    题目描述 输入正整数n 2 lt n lt 100 把阶乘n 1x2x3x xn分解成素因子相乘的形式 从小到大输出各个素数 2 3 5 的指数 你的程序应忽略比最大素因子更大的素数 否则末尾会有无穷对个0 样例输入 5 53 样例输出 5
  • 跨域问题的原理分析

    一 什么是跨域 当页面来源url 的协议 域名 端口 跟页面发出请求获取后端数据的url 的协议 域名 端口 只有要一个不同时 即为跨域 举个例子 我当前先请求blog csdn net nav lang到csdn服务器获取到一个csdn的
  • Caused by: org.springframework.context.ApplicationContextException: Unable to start ServletWebServer

    错误原因 SpringApplication run 中的类名书写错误 应该是写成springboot启动类的类名而不是其他的 如下所示 我启动类的类名为Main 那么在run方法中应该为Main class而不是其它 SpringBoot
  • RxPermissions简单使用

    RxPermissions简单使用 描述 随着社会的发展人们也开始重视对隐私的保护 谷歌也在Android6 0 sdk 23 增加了动态权限申请来保护广大用户的隐私 使我们开发者实现起来会很繁琐 代码量也会增多 但是对于程序员来说永远都是
  • JWT 身份认证优缺点分析以及常见问题解决方案

    JWT 身份认证优缺点分析以及常见问题解决方案 之前分享了一个使用 Spring Security 实现 JWT 身份认证的 Demo 文章地址 适合初学者入门 Spring Security With JWT 的 Demo Demo 非常
  • javascript基础第二天笔记

    JavaScript 基础 第2天 理解什么是流程控制 知道条件控制的种类并掌握其对应的语法规则 具备利用循环编写简易ATM取款机程序能力 运算符 语句 综合案例 运算符 算术运算符 数字是用来计算的 比如 乘法 除法 加法 减法 等等 所
  • Neo4j使用系列4

    Part4 1 Cypher基础1 类似于关系数据库中使用的SQL 是Neo4j使用的查询语言 1 特点 是一种声明式图形查询语言 富有表现力和高效的查询 更新和管理 设计简单 但功能强大 可以轻松表达高度复杂的数据库查询 Cypher的结
  • MySQL和Oracle时间取整

    按每15分钟时间取整 mysql SELECT now interval TIME TO SEC now mod 900 second from dual 其中now 可以替换为 你自己的 字段 oracle select sysdate
  • 第三方库(wordcloud为例)调用出现种种问题

    刚刚学习了python 想做点小东西练练手 python有很多好玩的东西 turtle库 wordcloud等等一系列我觉得都可以用来练练手并且真的是挺好玩 本来寻思也就十多行代码 肯定一会就能调试完 没想到 真的是我太天真 本来就不怎么会
  • 笔记本拓展外接显示器时 鼠标移动不到主显示器外的另一块屏上

    原因 显示面板 两个显示器图形表示 如下图带有标号的方块 摆放顺序不正确 把代表左边显示器的图标拖动到左侧即可
  • 从零到熟练编写LaTex数学公式,这两篇就够了

    第一篇 LaTex公式编辑方法 快速手敲一遍 熟悉常用操作 第二篇 CSDN官方参考文档 有不清楚的 随手查阅 在线公式编辑 实在打不出 就在线编辑吧
  • R语言系统教程(一):向量及其相关操作

    R语言系统教程 一 向量及其相关操作 前言 1 1 向量 Vector 赋值 1 10 4 5 6 3 1 6 4 21 7 运算 常用函数 1 2 Generate常用向量 Vector 等差数列 等间隔函数 重复函数 1 3 逻辑向量
  • coco 输出格式,MPII 输出格式,标注

    pose 1 数据集 coco 输出格式 MPII 输出格式 代码 详解 1 2 blobFromImage函数 1 数据集 BODY25 COCO MPI coco 输出格式 鼻子 0 颈部 1 右肩 2 右肘 3 右手腕 4 左肩 5
  • 阿里无影云电脑 试用评测

    总有些一些项目需要在家里和公司两头做 不管是用 svn git 云盘同步 还是U盘拷贝都是很麻烦的 背笔记本更累 以前一直想买个挂机宝 但那玩意的配置实在是低 又想说买个云电脑 玩游戏的那种 但价格贵的离谱 一直用vps将就 那性能大家都知
  • Java Collections.list()方法具有什么功能呢?

    转自 Java Collections list 方法具有什么功能呢 下文笔者讲述Collections list 方法的功能简介说明 如下所示 Collections list 方法的功能 将参数中的值转换为一个list对象 Collec
  • 主成分分析(PCA)方法原理介绍

    原文链接 http blog codinglabs org articles pca tutorial html
  • ElasticSearch 设置(一)发现和集群形成

    文章目录 发现和集群形成 发现 种子节点提供者 基于配置的种子主机提供者 基于文件的种子主机提供者 基于法定人数的选举 主节点的选举 投票配置 偶数个符合主节点的节点 设置初始投票配置 引导一个集群 选择集群名称 发布集群状态 集群故障检测
  • 分库分表ShardingSphere<三> _ 分布式事务

    目录 一 分布式事务 1 LOCAL事务 2 XA事务 3 BASE事务 柔性事务 二 示例 1 依赖jar包 2 配置XA事务 3 使用XA事务 三 参考资料 一 分布式事务 ShardingSphere提供三种事务类型 LOCAL 默认
  • MySQL之DML操作

    MySQL之DML操作 1 什么是DML操作 2 插入记录 insert 3 更新记录 update 4 删除记录 delete 1 什么是DML操作 DML是指数据操作语言 英文全称是Data Manipulation Language
  • 最难以理解的排序算法 - 堆排序(超详解)

    堆排序基本介绍 堆排序是利用堆这种数据结构而设计的一种排序算法 堆排序是一种选择排序 它的最坏 最好 平均时间复杂度均为O nlogn 它也是不稳定排序 要理解堆排序 必须先要理解堆这种数据结构 堆是具有以下性质的完全二叉树 每个结点的值都