1.堆是一个完全二叉树
2.任意非叶子节点的值大于等于(大顶堆)或小于等于(小顶堆)其左右孩子的值.
这就说明根元素的值是堆中最大或最小的.
如果我们输出堆顶元素, 然后对剩下的元素重新调整为一个堆, 如此直到输出所有元素, 便可完成排序了.
但是有个问题, 初始给定的数据不是一个堆啊, 所以我们要先将其初始化为堆.
因此, 实现堆排序需要解决两个问题: 1. 将一个无序序列构建为一个堆 2.输出堆顶元素后, 调整堆.
物理结构为一个数组, 逻辑结构为完全二叉树.
用 n 表示最大下标则:
array[0] 为堆顶
array[i] 的左孩子为 array[2*i+1], 右孩子为 array[2*i+2] i >= 0 && i <= (n-1)/2
1. 怎么构建一个堆呢?
也就是说我们要将一个最大/最小的元素放到堆顶
那就在堆顶 array[0], 左子树 array[1], 右子树 array[2] 中"选择"一个最大的放到 array[0]
那左子树 array[1] 的最大元素呢? 我想聪明的你应该想到了, 继续同样的操作就行了.
这里的"选择"就是一个调整堆的过程. 所以现在我们将问题转化为调整堆了.
2. 调整堆
我们给定序列 8, 9, 6, 7, 9, 7
创建的大顶堆如下, 这时 array[0] 即为此序列的最大元素
输出堆顶元素(和序列最后一个元素交换位置)
这时就破坏了大顶堆, 我们先调整(6, 9, 7), 选择一个最大的与根交换
由于交换又破坏了左子树, 那就继续调整, 直到所以遭破坏的树调整完毕即可
有可能破坏的树只有交换后的子树.
输出堆顶元素(交换), 此时堆顶元素为序列中次大的元素
同样调整堆
继续输出
调整
交换输出
调整
交换输出, 只剩下一个元素, 此时排序完成
如此, 我们可以看出大顶堆完成是从小到大的排序
稍微想一下, 小顶堆则完成的是从大到小的排序
此时我们再看创建大顶堆的过程, 就比较清楚了. 依次队非叶子节点调整
static void adjust_heap(int *keys, int first, int last) {
int root, child;
// child 开始为左孩子
for (root = first; (child = root * 2 + 1) <= last; root = child) {
// 在左右孩子中选择一个最大的, 此时最大的元素下标为 child
if (child + 1 < last && keys[child+1] > keys[child]) {
child = child + 1;
}
// 如果孩子中最大的比父节点大, 则与父节点交换, 并以此孩子节点为根继续调整 root = child
if (keys[child] > keys[root]) {
swap(&keys[child], &keys[root]);
// 如果在没交换的情况下, 父节点就是最大的, 则无需再继续调整
} else {
break;
}
}
}
void heap_sort(int *keys, int len) {
int i;
// 使len为最大索引
len -= 1;
// 构造大顶堆, 此时保证 keys[0] 为最大元素
for (i = (len-1)/ 2; i >= 0; --i)
adjust_heap(keys, i, len);
for (i = len; i > 0; ) {
// 将 keys[0] 放到尾部,
swap(&keys[i], &keys[0]);
// 移除最大元素, 调整堆
adjust_heap(keys, 0, --i);
}
}