堆的定义:
堆是一棵完全二叉树。每个结点的值都大于等于该结点的孩子结点的堆称为大顶堆,反之则称为小顶堆。
为了使用简便,我们就用数组Heap来存储堆结构,Heap[1]存储堆的根结点,如果当前遍历的结点为Heap[i],则Heap[2i]和Heap[2i+1]分别为其左右孩子结点。
堆排序
在了解了堆结构定义之后,我们就可以利用堆结构来进行排序。堆排序的基本思想就是,利用根结点一定是堆中最大或最小的结点,每次取出根结点后调整整个堆,这样就可以依次取出最大结点、次大结点....直到完成排序任务。
堆排序的主要过程:
- 调整初始序列为一个堆(本文选取为大顶堆)作为无序区。
- 从堆中取出根结点Heap[1]与无序区最后一个结点Heap[rear]交换,由此得到无序区Heap[1]到Heap[rear-1],和有序区Heap[rear]。
- 交换后的无序区可能不符合堆的性质,对无序区进行调整。
- 重复第2,3步,直到无序区元素全部被取出。
过程图如下:
实现代码如下:
/*对以front为根结点的完全二叉树进行大顶堆调整
* 末尾结点为rear*/
void HeapAdjust(int List[], int front, int rear)
{
/*f为当前遍历结点,i为其孩子结点*/
int f = front ;
int i = 2*front ;
int temp = List[front] ;
for( ; i <= rear; i = i*2) {
/*将两个孩子结点中较大的结点赋给i*/
if(i < rear && List[i] < List[i+1]) {
i++ ;
}
/*把根结点与其较大的孩子结点进行比较
* 根结点大则说明符合性质,否则将根结
* 点与较大孩子结点进行交换*/
if(temp >= List[i]) {
break ;
}
else {
List[f] = List[i] ;
/*进入较大孩子结点,持续向下筛选*/
f = i ;
}
}
/*插入根结点*/
List[f] = temp ;
}
/*对长度为num的序列进行堆排序*/
void HeapSort(int List[], int num)
{
int step = num/2 ; /*根据完全二叉树的性质,结点总数的1/2正好是其末尾叶子结点的双亲结点编号*/
int temp = 0 ;
/*从最末尾的子树开始向上进行堆调整
* 将初始序列调整为大顶堆*/
for( ; step > 0; step--) {
HeapAdjust(List, step, num) ;
}
/*依次取出堆顶*/
for(step = num+1; step > 0; step--) {
/*将堆顶元素与末尾元素交换*/
temp = List[step] ;
List[step] = List[1] ;
List[1] = temp ;
/*对剩余元素进行堆调整*/
HeapAdjust(List, 1, step-1) ;
}
}
总结
堆排序是选择排序的一种,它利用数组可以随机选取元素的特性快速地创建大顶堆然后依次选取。堆排序的时间开销主要是初始序列建堆和选取根结点后的反复建堆过程,其平均时间复杂度为O(n*log n)。