摘要

堆排序需要用到一种数据结构,大顶堆。大顶堆是一种二叉树结构,本质是父节点的数大于它的左右子节点的数,左右子节点的大小顺序不限制,也就是根节点是最大的值。

这里就是不断的将大顶堆的根节点的元素和尾部元素交换,交换到大顶堆没有可以被交换的元素为止。后面再说大顶堆的逻辑。

逻辑

首先将序列通过大顶堆排序。然后不断的从堆中取出顶部元素放在尾部,直到大顶堆元素为空。

流程

  1. 对序列进行原地建堆操作
  2. 重复下面操作,直到堆元素数量为 1
    1. 交换堆顶元素与尾元素
    2. 堆的元素数量减 1
    3. 对 0 位置进行 1 次 自下而上的下滤

下面在代码中解释原地建堆自下而上的下滤这两个词的逻辑。

实现

首先进行原地建堆。原地建堆是先将序列按照大顶堆的排序逻辑处理序列。

大顶堆的序列逻辑是父节点的值大于它的左右子节点的值,可以想象成一个二叉树。这里的原地排序用到了siftDown方法,而且在循环中只循环到序列一半数量,为什么?这个在下面看siftDown方法时详细探究一下。


// 原地建堆
// 自下而上的下滤
heapSize = array.length;
for (int i = (heapSize >> 1) - 1; i >= 0; i--) {
siftDown(i);
}

交换堆顶和尾部元素,然后将需要比较的序列元素数量减少1,并将要进行比较的序列再使用siftDown方法过滤,保持序列的大顶堆的性质。然后继续开始的交换,直到可以比较的序列数量为 1 就截止。

while (heapSize > 1) {
// 交换堆顶元素和尾部元素
swap(0, --heapSize); // 对 0 位置进行 siftDown(恢复堆的性质)
siftDown(0);
}

大顶堆的 siftDown 方法

这里来探究一下siftDown(下滤)。

二叉树的父节点和子节点的关系符合这样的公式

  • leftChilder = partner * 2 + 1
  • rightChilder = parnter * 2 + 1 + 1
  • half (叶子)节点的数量是总节点数量的 1/2

siftDown 方法主要是将 index 位置上的元素放在合适的位置上。那么什么位置是合适的位置呢

依据大顶堆的父节点值大于左右子节点的值的性质来看,只要是保证 index 位置的元素大于它的左右子节点就好。

看下面代码,如果 index < half 才进行循环比较,那么就有一个问题,index >= half 为什么不用比较

这就要提到很巧妙的点,首先看大顶堆的性质,左右子节点没有具体顺序的要求,其次子节点的值小于父节点。那么就可以依据二叉树的叶子节点性质,如果index的位置是在叶子节点位置,那么就本来比它的父节点要小,就不用比较(这个是建立在序列本来符合大顶堆的顺序,出现一个位置的元素有变化时进行的过滤处理)。

这也是上面的原地排序中,只从一半的位置开始,是因为从这个位置开始,肯定会给它的子节点比较,过滤出大的,并放在合适位置。

代码中有三个巧妙的点

  1. 循环从序列的一半位置开始比较,如果位置不在前半部分,就不进行比较,这个在上面分析过
  2. 在比较的时候,获取到它左右子节点中最大的节点比较。在获取右子节点的时候看右子节点是否存在rightIndex<heapSize。因为大顶堆是符合完全二叉树的(尽量往左子树安排元素)。
  3. 说是二叉树,但是没有实际的节点,还是一个线性序列,通过公式来获取左右子树的位置,这个就是心中有树,没有树也是树

/*
* 让 index 位置的元素下滤
*/
private void siftDown(int index) {
E element = array[index]; int half = heapSize >> 1; // 取出非叶子节点
// 第一个叶子结点的索引 == 非叶子节点的数量
// 必须保证 index 是非叶子节点
while (index < half) {
// index 的节点有2种情况
// 1、只有左子节点
// 2、同时有左右子节点 // 默认左子节点跟它进行比较
int childIndex = (index << 1) + 1;
E child = array[childIndex];
// 右子节点
int rightIndex = childIndex + 1;
if (rightIndex < heapSize && cmp(array[rightIndex], child) > 0) {
child = array[ childIndex = rightIndex];
} if (cmp(child, element) < 0) break; // 将子节点存放到index位置
array[index] = child;
// 重新设置 index
index = childIndex;
}
array[index] = element;
}

时间和空间复杂度

  • 最好、平均时间复杂度:O(nlogn)
  • 最坏时间复杂度:O((nlogn)
  • 空间复杂度:O(1)
  • 属于不稳定排序

题外话

这次的排序用到了二叉树大顶堆的一些知识,可能看下来有诸多疑问,这里就先请诸位看官有个印象,后续我会分享二叉树的知识,然后在回过头来看堆排序,会让你思路大开。

数据结构与算法-排序(六)堆排序(Heap Sort)的更多相关文章

  1. 小小c#算法题 - 7 - 堆排序 (Heap Sort)

    在讨论堆排序之前,我们先来讨论一下另外一种排序算法——插入排序.插入排序的逻辑相当简单,先遍历一遍数组找到最小值,然后将这个最小值跟第一个元素交换.然后遍历第一个元素之后的n-1个元素,得到这n-1个 ...

  2. Python入门篇-数据结构堆排序Heap Sort

    Python入门篇-数据结构堆排序Heap Sort 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.堆Heap 堆是一个完全二叉树 每个非叶子结点都要大于或者等于其左右孩子结点 ...

  3. 数据结构 - 堆排序(heap sort) 具体解释 及 代码(C++)

    堆排序(heap sort) 具体解释 及 代码(C++) 本文地址: http://blog.csdn.net/caroline_wendy 堆排序包括两个步骤: 第一步: 是建立大顶堆(从大到小排 ...

  4. SDUT-3403_数据结构实验之排序六:希尔排序

    数据结构实验之排序六:希尔排序 Time Limit: 1000 ms Memory Limit: 65536 KiB Problem Description 我们已经学习了各种排序方法,知道在不同的 ...

  5. SDUT OJ 3403 数据结构实验之排序六:希尔排序

    数据结构实验之排序六:希尔排序 Time Limit: 1000 ms Memory Limit: 65536 KiB Submit Statistic Discuss Problem Descrip ...

  6. SDUT 3403 数据结构实验之排序六:希尔排序

    数据结构实验之排序六:希尔排序 Time Limit: 1000MS Memory Limit: 65536KB Submit Statistic Problem Description 我们已经学习 ...

  7. SDUT OJ 数据结构实验之排序三:bucket sort

    数据结构实验之排序三:bucket sort Time Limit: 250 ms Memory Limit: 65536 KiB Submit Statistic Discuss Problem D ...

  8. SDUT 3400 数据结构实验之排序三:bucket sort

    数据结构实验之排序三:bucket sort Time Limit: 150MS Memory Limit: 65536KB Submit Statistic Problem Description ...

  9. SDUT-3400_数据结构实验之排序三:bucket sort

    数据结构实验之排序三:bucket sort Time Limit: 250 ms Memory Limit: 65536 KiB Problem Description 根据人口普查结果,知道目前淄 ...

  10. 堆排序 Heap Sort

    堆排序虽然叫heap sort,但是和内存上的那个heap并没有实际关系.算法上,堆排序一般使用数组的形式来实现,即binary heap. 我们可以将堆排序所使用的堆int[] heap视为一个完全 ...

随机推荐

  1. Vue3全家桶升级指南二ref、toRef、toRefs的区别

    ref是对原始数据的拷贝,当修改ref数据时,模板中的视图会发生改变,但是原始数据并不会改变. toRef是对原始数据的引用,修改toRef数据时,原始数据也会发生改变,但是视图并不会更新. 在vue ...

  2. Oracle查询wm_concat返回[oracle.sql.CLOB@7D6414ed]之坑!

    在orcale中使用wm_concat函数将字段分组连接. 在linux环境下需要将wm_concat(字段)进行to_char(wm_concat(字段))处理.

  3. CRM企业管理系统对于企业的价值

    对于企业来说,一个完整的工作流程可以概括为三个阶段:售前.售中.售后.每个阶段都需要不同的管理.此外,客户关系管理客户关系管理系统可以帮助企业在这三个阶段进行业务管理和客户管理,帮助企业更好地运作,增 ...

  4. CentOS-Docker安装Elasticsearch(单点)

    下载镜像 $ docker pull elasticsearch:7.6.0 运行镜像 $ docker run --restart=always --name elasticsearch -d -p ...

  5. Docker:docker部署PXC-5.7.21(mysql5.7.21)集群搭建负载均衡实现双机热部署方案

    单节点数据库弊端 大型互联网程序用户群体庞大,所以架构必须要特殊设计 单节点的数据库无法满足性能上的要求 单节点的数据库没有冗余设计,无法满足高可用 推荐Mysql集群部署方案 PXC (Percon ...

  6. Java实验项目三——简单工厂模式

    Program: 请采用采用简单工厂设计模式,为某个汽车销售店设计汽车销售系统,接口car至少有方法print(), 三个汽车类:宝马.奥迪.大众 (属性:品牌,价格),在测试类中根据客户要求购买的汽 ...

  7. 南京大学计算机基础 X64函数调用和链接器原理和可重定位的文件.o

    一. 1.函数调用差别 X64的函数调用,和X86函数调用区别,在于参数的传递不一样了,X64的参数传递不在依靠栈来传,而是寄存器,不过还是具有局限性的 比如只能允许六个寄存器来传,分别是RDI,RS ...

  8. challenge

    一.查壳 无壳的64位linux文件 二.拖入ida静态分析 F5反编译一下. 发现这段代码实际意义就是把字符串传进v7当中.v7的值就是flag值. 这个函数整体分析下来,各种递归,尝试用c语言来复 ...

  9. (精)题解 guP2860 [USACO06JAN]冗余路径Redundant Paths

    (写题解不容易,来我的博客玩玩咯qwq~) 该题考察的知识点是边双连通分量 边双连通分量即一个无向图中,去掉一条边后仍互相连通的极大子图.(单独的一个点也可能是一个边双连通分量) 换言之,一个边双连通 ...

  10. WIN10家庭版 访问WINXP 共享打印机

    WIN10家庭版 1.安装对应的打印机驱动 2.打开WIN10计算机---在地址栏中输入:\\计算机XP名称,显示对应的共享资源,直接选择即可.如果无法访问则进行如下第三步 3.设置过程 开始 -设置 ...