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

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(使用前将#替换为@)

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

  • C语言/C++实现栈操作

    一 栈的概念 栈是一种常用的数据结构 它遵循先入后出 Last In First Out LIFO 的原则 栈的操作只在栈的一端进行 该端被称为栈顶 而另一端称为栈底 栈的基本操作包括压栈 入栈 push 和弹栈 出栈 pop 分别用于将元
  • 数据库多维迭代算法

    关键词 数据库 迭代 递归 多维 一 两种传统的数据库迭代结构算法 对于数据库的迭代结构 有两种传统的算法 递归算法和边界算法 比如对于下面图1的结构 图1 递归算法的数据结构如表1所示 节点id 节点值 父节点id 1 1111 2 3
  • Mysql 数据库

    数据库基础 1 什么是数据库 用来存储数据 数据库可在硬盘及内存中存储数据 数据库与文件存储数据的区别 数据库本质也是通过文件来存储数据 数据库的概念就是系统的管理存储数据的文件 数据库介绍 本质就是存储数据的C S架构的socket套接字
  • BP神经网络与Python实现

    人工神经网络是一种经典的机器学习模型 随着深度学习的发展神经网络模型日益完善 联想大家熟悉的回归问题 神经网络模型实际上是根据训练样本创造出一个多维输入多维输出的函数 并使用该函数进行预测 网络的训练过程即为调节该函数参数提高预测精度的过程
  • 4399 C++笔试题

    1 写出一个函数 取到链表中倒数第二个节点 双链表 node getSec List mylist return mylist m tail gt m prev m prev为链表前指针 单链表 node getSec List mylis
  • (笔试前准备)字符串匹配算法总结

    我想说一句 我日 我讨厌KMP KMP虽然经典 但是理解起来极其复杂 好不容易理解好了 便起码来巨麻烦 老子就是今天图书馆在写了几个小时才勉强写了一个有bug的 效率不高的KMP 特别是计算next数组的部分 其实 比KMP算法速度快的算法
  • 微软2013暑假实习生笔试题

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

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

    Author Maddock Date 2015 04 22 转载请注明出处 http www cnblogs com adong7639 p 4446828 html DNG格式基本概念 DNG格式是在TIFF的基础上扩展出来的 要了解D
  • Python 实现列队

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

    上一期文章分享完了图的两种遍历方式 也是两种很重要的算法 DFS和BFS 这两种算法的应用和重要性我就不多说了 内行的人懂的都懂 今天这文章重要就是来上机实现这两种算法 又由于这两种算法都可以由邻接矩阵和邻接表来表示 博主分享的代码都是上机
  • 链表面试题(一):反转链表的算法实现

    关于链表的考察 链表是面试里面经常涉及到的考点 因为链表的结构相比于Hashmap Hashtable Concurrenthashmap或者图等数据结构简单许多 对于后者更多面试的侧重点在于其底层实现 比如Hashmap中Entry
  • 图 - Java实现无向带权图的邻接矩阵表示法

    图 Java实现无向带权图的邻接矩阵表示法 1 图 1 1 图的介绍 图 Graph 是一种复杂的非线性表结构 图中的元素我们就叫做顶点 vertex 图中的一个顶点可以与任意其他顶点建立连接关系 我们把这种建立的关系叫做边 edge 跟顶
  • Unique Binary Search Trees -- LeetCode

    原题链接 http oj leetcode com problems unique binary search trees 这道题要求可行的二叉查找树的数量 其实二叉查找树可以任意取根 只要满足中序遍历有序的要求就可以 从处理子问题的角度来
  • 堆栈01--用两个栈实现队列

    堆栈01 用两个栈实现队列 jz05 题目概述 解析 参考答案 注意事项 说明 题目概述 算法说明 用两个栈来实现一个队列 完成队列的Push和Pop操作 队列中的元素为int类型 测试用例 队列先进先出 输入 1 2 输出 1 2 解析
  • 算法学习——贪心算法之币种统计

    算法描述 币种统计 单位给每一位员工发工资 精确到元 为了保证不临时换零钱 使得每个员工取款的张数最少 在取工资前统计所有员工所需要的各种票面的张数 约定票种为100 50 20 10 5 2 1元 并验证币种统计是否正确 算法思路 算法描
  • Linux下进程退出的几种形式

    进程退出 Linux 下进程的退出分为正常退出和异常退出两种 1 正常退出 a 在main 函数中执行return b 调用exit 函数 c 调用 exit 函数 2 异常退出 a 调用about函数 b 进程收到某个信号 而该信号使程序
  • 【试题】排列组合

    在写一个远程的代码 如果本地有M个显示器 远程有N个显示器 M lt N 依据分辨率 显示器刷新频率等要求 需要对远程的N个显示器进行最佳分辨率修改 之后 需要从N个远程显示器中选择M个 跟本地显示器进行一对一的匹配 即从 A N M N
  • 基数排序代码实现

    详情请看排序总结 传送门 https blog csdn net m0 52711790 article details 121914543 基数排序的知识点我就不贴出来 相信都能搜到对应概念解释 下面就直接上代码 代码解释其实也很清晰了
  • 浅谈归并排序:合并 K 个升序链表的归并解法

    在面试中遇到了这道题 如何实现多个升序链表的合并 这是 LeetCode 上的一道原题 题目具体如下 用归并实现合并 K 个升序链表 LeetCode 23 合并K个升序链表 给你一个链表数组 每个链表都已经按升序排列 请你将所有链表合并到

随机推荐

  • 算法——因子和阶乘

    题目描述 输入正整数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 它也是不稳定排序 要理解堆排序 必须先要理解堆这种数据结构 堆是具有以下性质的完全二叉树 每个结点的值都