准备工作
1.堆的概念:
堆通常是一个可以被看做一棵树的数组对象。在队列中,调度程序反复提取队列中第一个作业并运行,因而实际情况中某些时间较短的任务将等待很长时间才能结束,或者某些不短小,但具有重要性的作业,同样应当具有优先权。堆即为解决此类问题设计的一种数据结构。(wiki上的定义)
在堆排序中,我们用到的是二叉堆(是一棵二叉树的一种)。
2.最大堆(属于二叉堆):
每个父结点的值都大于它的左右两个子结点的值,在根节点存储着堆里所有数的最大值。
堆排序
1.堆修复:
0 4
此时我们要将其修复成一个最大堆,叫做堆修复。
设堆存在数组a[]中,下标从1开始,共有 n 个节点。
我们先比较一下根节点和其左右儿子的大小,找出最大的那个数的下标largest,有两种情况:
(1)largest 不为根节点(即是左右儿子中的一个)
此时,root (根节点)和 a[largest]交换,之后对largest继续修复(采用递归)。
(2)largest是根节点,此时表明修复堆已经完成。
这样是不是堆就修复完了呢? 当然不是。
注意到我们如果修复到了叶子节点,此时该修复工作也完成了,所以当左右某个儿子的下标大于了n时,本次修复结束(我们是采用递归来修复的,所以本次修复结束不意味着总修复结束)。
接下来我们就可以用代码实现了:
void repair_heap(int a[], int pos, int sz)
{
int l = pos * 2;
int r = pos * 2 + 1;
int largest;
if(l <= sz && a[l] > a[pos])
largest = l;
else largest = pos;
if(r <= sz && a[r] > a[largest])
largest = r;
if(largest != pos)
{
swap(a[largest], a[pos]);
repair_heap(a, largest, sz);
}
}
不难看出,这一步的复杂度为O(lgn).
sz(size)的作用在后面我们会看到。2.建立最大堆:
void build_maxheap(int a[], int pos)
{
int l = pos * 2;
int r = pos * 2 + 1;
if( l <= n )
build_maxheap( a, l );
if( r <= n)
build_maxheap( a, r );
repair_heap(a, pos, n);
}
3.排序:
怎么实现将最大值存储后再排出堆呢?
我们将第 n 个元素和 a[1] 交换,然后再让堆的节点数减1(这个就通过sz来实现,我们尽量不改变n)。
接下来我们从a[ 1 ]开始修复堆就行了,以此循环,最终达到排序的效果。
代码如下:
void heap_sort(int a[])
{
int heapsize = n;
while(heapsize > 1)
{
swap(a[heapsize--], a[1]);
repair_heap(a, 1, heapsize);
}
}
这一步复杂度为 O(nlgn)。