堆和堆排序
堆排序结合了合并排序和插入排序的优点,其时间复杂度为O(n*logn),且是一种原地排序算法(在任何时候,数组中只有常数个元素存储在输入数组以外)
堆排序采用了名为“堆”的数据结构,分为大顶堆和小顶堆。堆排序采用了大顶堆,其定义是:一颗完全二叉树,且除根结点外的任意结点的值至多和其父结点的值一样大。小顶堆组织方式刚好相反,其通常用在构造优先队列时使用
为方面起见,实际代码中用数组表示堆
堆的关键操作是“维持大顶堆性质”(记为MAX_HEAPIFY),即堆中结点变动后,对堆进行适当调整,使其重新满足最大堆的性质。MAX_HEAPIFY是一种递归操作,其步骤如下:
- 比较结点a[k]与其左右子树根结点a[2k+1], a[2k+2]的大小,若a[k]不是最大的,则将a[k]与最大值结点交换位置
- 重复上面的步骤,直至a[k]比其左右子树根结点都大或为叶节点为止
然后是“建堆”操作,具体方法为:
从完全二叉树的最后一个非叶结点a[n/2-1]开始,逆遍历至根结点结束,逐个进行MAX_HEAPIFY操作
了解以上两个操作后,堆排序步骤如下:
- 建立最大堆
- 将根结点与最后一个叶结点交换,堆大小减1
- 对新的根结点使用MAX_HEAPIFY操作
- 重复步骤2,3直到堆为空
代码
void swap(int * a, int * b){
assert(a != NULL);
assert(b != NULL);
if(a != b){
*a ^= *b;
*b ^= *a;
*a ^= *b;
}
}
void max_heapfy(int a[], int n, int k){ //k是扰乱大顶堆性质的结点的下标
assert(a != NULL);
assert(n > 0);
assert(k >= 0 && k < n);
int max = k;
if(k*2 < n && a[k*2] > a[max]) max = k*2;
if(k*2+1 < n && a[k*2+1] > a[max]) max = k*2+1; //判断哪个是最大结点
if(max != k){ //递归进行大顶堆性质维持
swap(&a[k], &a[max]);
max_heapfy(a, n, max);
}
}
void heap_sort(int a[], int n){
assert(a != NULL);
assert(n > 0);
for(int i = n/2-1; i >= 0; i--) //建堆,最后一个非叶结点为a[n/2-1]
max_heapfy(a, n, i);
for(int i = n-1; i >= 1; i--){ //排序时,每次交换堆顶元素和堆尾元素,然后进行大顶堆性质维持,堆的大小是不断减小的
swap(&a[0], &a[i]);
max_heapfy(a, i, 0); //堆的大小不断减小
}
}