堆排序一种是基于二叉堆的排序。本文将从优先队列讲起,循序渐进的实现堆排序。这也是《算法》第四版上讲解堆排序的大致章节结构。另外,本文所有的图都来自于此书。
优先队列
普通队列是一种先进先出的数据结构,先放进队列的元素取值时优先被取出来。而优先队列是一种具有最高优先级元素先出的数据结构,比如每次取值都取最大的元素。
优先队列有两个核心方法,一个是insert(val)
向队列中添加元素,另外一个是delMax()
删除最大元素(优先级最高的)并返回。可以考虑使用数组来存储优先队列的数据。
优先队列有如下三种实现方式:
- 有序数组(调用insert方法时使用类似插入排序把新增元素放到正确位置,调用delMax方法时直接取数组最后一个元素,因为此时元素已经是有序状态)
- 无序数组(调用insert方法时直接把元素放到数组中,调用dexMax方法时使用类似选择排序的方法从数组中找出最大元素,删除并返回)
- 堆
堆的算法
优先队列可以使用一棵堆有序的二叉树来解决。什么叫做堆有序呢?当一棵二叉树的每个几点都大于它的两个子节点时,它被称为堆有序。那么,根节点就是堆有序的二叉树中的最大节点。
二叉堆可以使用数组来存储。为了方便起见,根节点放在位置1处(位置0不使用),它的两个子节点分别放在位置2和位置3。同理可知,在一个堆中,位置k的节点的父节点的位置为k/2,而它的两个子节点的位置则分别为2k和2k+1.我们就可以这样在树的上下移动:访问a[k]节点的上一层就令k等于k/2,向下一层就令k等于2k或2k+1.
如下图所示:
使用二叉堆,我们就能实现对数级别的插入元素和删除最大元素。
由下自上的堆有序化(上浮)
当调用优先队列的insert
方法时,我们首先把元素放置到数组的结尾,然后再把该元素上浮到正确的节点,最终形成堆有序状态。
代码如下:
1 | //下标为k的元素上浮到正确位置 |
由上至下的堆有序化(下沉)
当调用优先队列的delMax
方法时,我们下标为1的元素(最大元素)和数组最后一个元素交换,返回最大元素,数组长度减去1,即删除最后一个元素。此时的位置1元素不一定是最大元素,就需要下沉到正确位置,重新构造有序堆。
代码如下:
1 | //下标为k的元素下沉到正确位置 |
堆有序化示例图:
堆的操作示例图:
优先队列实现
理解了实现堆有序的上浮和下沉两种方法,优先队列的实现就轻而易举了,代码如下:
1 | /** |
示例图:
堆排序
理解了优先队列,堆排序的逻辑十分简单。第一步:让数组形成堆有序状态;第二步:把堆顶的元素放到数组最末尾,末尾的放到堆顶,在剩下的元素中下沉到正确位置,重复操作即可。
代码如下:
1 | /** |
示例图:
完整代码
Javascript实现
1 | /** |
Java实现
1 | package com.algs; |
总结
- 不稳定。
- 原地排序。
- 时间复杂度为NlogN
- 空间复杂度为l
GitHub:https://github.com/AlbertKnag/algs-practice
上一篇:排序算法07:三向快速排序
下一篇:排序算法09:总结