前言
这次我们介绍另一种时间复杂度为 O(nlogn) 的选择类排序方法叫做堆排序。
我将从以下几个方面介绍:
- 堆的结构
- 堆排序
- 优化的堆排序
- 原地堆排序
- 堆的应用
堆的结构
什么是堆?我给出了百度的定义,如下:
堆(Heap)是计算机科学中一类特殊的数据结构的统称。堆通常是一个可以被看做一棵 完全二叉树 的数组对象。
堆总是满足下列性质:
- 堆中某个节点的值总是不大于或不小于其父节点的值。
- 堆总是一棵完全二叉树。
将根节点最大的堆叫做最大堆,根节点最小的堆叫做最小堆。
下图展示了一个最大堆的结构:
![d855d6e04b2fa03498e9f5ae68acdcbf.png](https://img-blog.csdnimg.cn/img_convert/d855d6e04b2fa03498e9f5ae68acdcbf.png)
可见,堆中某个节点的值总是小于等于其父节点的值。
由于堆是一棵完全二叉树,因此我们可以对每一层进行编号,如下:
![2c1f912de34752bfb34665b620d51372.png](https://img-blog.csdnimg.cn/img_convert/2c1f912de34752bfb34665b620d51372.png)
我们完全可以使用数组存放这些元素,那如何确定存放的位置呢?利用如下公式:
父节点:parent(i) = (i-1)/2
左孩子:leftChild(i) = 2*i+1
右孩子:rightChild(i) = 2*i+2
相关代码如下:
private int parent(int index) { return (index - 1) / 2;}private int leftChild(int index) { return index * 2 + 1;}private int rightChild(int index) { return index * 2 + 2;}
添加元素
向堆中添加元素的步骤如下:
- 将新元素放到数组的末尾。
- 获取新元素的父亲节点在数组中的位置,比较新元素和父亲节点的值,如果父亲节点的值小于新元素的值,那么两者交换。以此类推,不断向上比较,直到根节点结束。
下图展示了添加元素的过程:
![64aa546077a94ef5bf73602f748d1dfa.png](https://img-blog.csdnimg.cn/img_convert/64aa546077a94ef5bf73602f748d1dfa.png)
添加元素的过程也叫做 siftUp ,代码如下:
// Array是自己实现的动态数组private Array data;public void add(E e) { data.addLast(e); siftUp(data.getSize() - 1);}private void siftUp(int k) { while (k > 0 && data.get(parent(k)).compareTo(data.get(k)) < 0) { data.swap(k, parent(k)); k = parent(k); }}
删除元素
删除元素其实就是删除堆顶的元素,步骤如下:
- 让数组最后一个元素和数组第一个元素(堆顶元素)交换。