1.介绍
“堆排序”是一种选择排序,顾名思义,它是一种基于 堆 这一数据结构的一种有效的排序算法。
堆(以“最大堆”为例)中,每个节点的值 大于 其左右孩子节点的值,所以它的根结点是整个堆中最大的节点,如下图所示(其中我们对堆中的结点按层进行编号,将这种逻辑结构映射到数组中就如右图----均不适用数组第一个位置)
堆有序:当一棵二叉树的每个结点都大于等于它的两个子结点时,则成为堆有序。
二叉堆:是一组能够用堆有序的完全二叉树排序的元素,并在数组中按照层级储存(不适用数组的第一个位置)
根结点:1
父结点:
⌊
k
/
2
⌋
\lfloor k/2 \rfloor
⌊k/2⌋
子结点:
2
k
2k
2k 和
2
k
+
1
2k+1
2k+1
树的高度:一棵大小为
N
N
N的完全二叉树的高度为
⌊
l
g
N
⌋
\lfloor lgN \rfloor
⌊lgN⌋
2.堆的算法
2.1 由下至上的堆有序化(上浮)
如果堆有序的状态因为某个结点变得比它的父结点更大而被打破,此时就需要将其与父结点交换,交换之后,这个结点就比它的父结点大(一个是曾经的父结点,另外一个比它小,因为另外一个结点比原本的父结点要小),通过循环这一个操作,既可以恢复堆有序的状态。
//上浮
private void swim(int k) {
while(k > 1 && less(k/2, k)) {
exch(k/2, k);
k = k/2;
}
}
2.2 由上至下的堆有序化(下沉)
如果堆有序的状态因为某个结点变得比它的两个子结点或者其中一个更小而打破,此时就需要将该结点和它的子结点中的较大者进行交换来恢复堆。算法如下:
- 先找到子结点中较大者
- 比较该结点和子结点中较大者的大小,若该结点大则表示堆恢复,结束函数;否则将该结点和子结点较大者交换。
//下沉 private void sink(int k) { while(2*k <= N) { int j = 2*k; if(j < N && less(j, j+1))j++; if(!less(k, j))break; exch(k, j); k = j; } }
2.3 向堆中插入元素
算法步骤如下:
- 堆的规模增加一,将待插入元素放到堆尾位置
- 对新插入的元素调用上浮算法,去到正确的位置之后则插入完成,堆重新恢复有序。
2.4 从堆中删除最大元素
算法步骤如下:
- 从数组顶端删除最大的元素,并将堆的最后一个元素放到顶端,堆的规模减一
- 将刚放上来的堆顶元素调用下沉算法,下沉到正确的位置,则删除完成,堆重新恢复有序
对于一个含有 N N N个元素的堆,插入元素操作只需不超过 ( l g N + 1 ) (lgN+1) (lgN+1)次比较,删除最大元素的操作需要不超过 2 l g N 2lgN 2lgN次比较
3.堆排序特性
稳定性 | 时间复杂度(平均) | 时间复杂度(最坏) | 时间复杂度(最好) | 备注 |
---|---|---|---|---|
不稳定 | O ( n log 2 n ) O(n \log_2 n) O(nlog2n) | O ( n log 2 n ) O(n \log_2 n) O(nlog2n) | O ( n log 2 n ) O(n \log_2 n) O(nlog2n) |
4.堆排序算法分析
- 通过“最大堆”的构造方法,将输入的无序数组构造成一个“最大堆”,作为算法开始的初始状态
- 将 堆顶元素(最大值) 和堆的最后一个元素调换位置,这样就将最大元素"沉"到数组末端;
- 堆的规模减一,之后“堆顶元素”通过“下沉算法”将堆恢复成最大堆
- 重复以上两个步骤,知道堆的规模为“1”,则得到一个从小到大排序的数组
用下沉操作有 N N N个元素构造堆,只需要少于 2 N 2N 2N次比较以及少于 N N N次交换
//堆排序
public void sort(Comparable[] a) {
int N = a.length;
//从最后一个父结点开始,依次下沉,则完成堆的构造
for(int k = N/2; k >= 1; k--) {
sink(k);
}
while(N > 1) {
exch(1, N--); //将 堆顶元素(最大值) 和堆的最后一个元素调换位置,这样就将最大元素"沉"到数组末端;堆的规模减一
sink(1); //之后“堆顶元素”通过“下沉算法”将堆恢复成最大堆
}
}
5.全部代码如下
public class MaxPQ<Key extends Comparable<Key>> {
private Key[] pq;
private int N = 0; //存储pq[1...N]中,pq[0]没有使用
public MaxPQ(int maxN) {pq = (Key[])new Comparable[maxN + 1];}
public boolean isEmpty() {return N == 0;}
public int size() {return N;}
//上浮
private void swim(int k) {
while(k > 1 && less(k/2, k)) {
exch(k/2, k);
k = k/2;
}
}
//下沉
private void sink(int k) {
while(2*k <= N) {
int j = 2*k;
if(j < N && less(j, j+1))j++;
if(!less(k, j))break;
exch(k, j);
k = j;
}
}
//插入
public void insert(Key v) {
pq[++N] = v;
swim(N);
}
//删除最大元素
public Key delMax() {
Key max = pq[1]; //从根结点得到最大元素
pq[1] = pq[N--]; //最后一个结点放到最上方
pq[N+1] = null; //防止对象游离
sink(1); //恢复堆的有序性
return max;
}
//堆排序
public void sort(Comparable[] a) {
int N = a.length;
for(int k = N/2; k >= 1; k--) {
sink(k);
}
while(N > 1) {
exch(1, N--);
sink(1);
}
}
//辅助方法
//比较
private boolean less(int i, int j) {return pq[i].compareTo(pq[j]) < 0;}
//交换
private void exch(int i, int j) {Key t = pq[i]; pq[i] = pq[j]; pq[j] = t;}
}
参考博客
参考书籍:《算法·第四版》