声明:本文都以大根堆为例。
1. 堆的基本概念
堆就是利用完全二叉树的结构来维护的一维数组。
大顶堆:每个结点的值都大于或等于其左右孩子结点的值。
小顶堆:每个结点的值都小于或等于其左右孩子结点的值。
排序,求升序用大顶堆,求降序用小顶堆。
2. 堆——二叉树的顺序存储
2.1 完全二叉树的顺序存储特性回顾
若点编号从1开始,共有n个节点,则对于点i:
- 左儿子为2i;(编号从0开始,左儿子为2i+1)
- 右儿子为2i+1;(编号从0开始,右儿子为2i+2)
- 父亲节点为floor(i/2);
- 所在层次为ceil(log2(i+1))或floor(log2(i))+1;
- 2*i<=n表i有左儿子;
- 2*i+1<=n表i有右儿子;
- i>n/2表i为叶子节点(参考左右孩子的编号);
n/2为最后一个非叶子节点;
2.2 存储方式
把树存储在一个数组中,堆排序在此数组中原地排序,S(n)=O(1),其和逻辑中的树的对应关系见图。
2.3 建堆时的排序方式
调整思路:
- 非叶子节点不满足“大于子节点的特征”,需要调整;
如果先调整上面的节点,后调整下面的,那么下面调整完又要调整上面的,所以从下面的节点开始倒序调整;- 2中被划掉的推测有误,因为倒序调整时,上面的节点被换到下面来后,还是可能因太小不断“下沉”;倒序调整是为了让子树先满足性质,再不断扩大这个满足性质的子树。
3. 利用大根堆进行堆排序
3.1 方法
step1: 建堆,即把已有数组调整成大根堆。
step2: 利用大根堆给出有序数组,调整n次,每次把堆顶和堆底元素互换,然后下沉新的堆顶(每次堆都变小);第一次把根a[1]和a[n]调换,然后下沉新的a[1];第i次把根a[1]和数组的n+1-i个数a[n+1-i]调换,然后下沉新的a[1],此时a[n+2-i…n]都是有序的了;调整完的数组是上升数组。
3.2 算法分析
- 建堆的时间复杂度为O(n)。
- 建堆时排序的时间复杂度为O(n*log2n) (插入一个是O(log2n))。
- 堆排序总的时间复杂度为O(n)+O(nlog2n) =O(n*log2n) 。
- 空间复杂度O(1)。