堆的定义
n个关键字序列:成为堆,当且仅当该序列满足以下性质(简称为堆性质):
(1) 且
(2) 且
满足(1)情况的堆为小根堆,满足第二种情况的为大根堆。下面讨论的是大根堆。
排序思路
堆排序的过程与直接选择排序类似,只是挑选最大或是最小元素的不同,这里采用大根堆,每次挑选最大元素归位。挑选最大元素的方法是将数组中存储的数据看成是一棵完全二叉树,利用完全二叉树中双亲结点和孩子结点之间的内在关系来选择关键字最大元素(为了与二叉树顺序存储结构保持一致,堆排序的数据序列的下标从1开始)
数组和二叉树的对应关系:{6,8,7,9,0,1,3,2,4,5}
堆排序的关键是构建初始堆:初始堆建立是通过循环,从下往上建立的。假设完全二叉树a[n]的某一个结点为a[i],它的左子树、右子树已经是堆,接下来需要将它的左右子节点a[2i]和a[2i+1]进行比较,选择一个最大的,与a[i]比较,如果a[i]较小,将其与最大孩子关键字互换,可能会破坏下一级的堆,所以继续采用上述方法构造下一级的堆,直到完全二叉树的i结点构造成堆为止。
i取n/2~1,反复够利用上诉方法建堆。
调整堆的算法sift()如下:
void sift(int a[],int low,int high){
int i=low,j=2*i;//a[j]是a[i]的左孩子
int tmp;
tmp=a[i];
while(j<=high){
//若有孩子较大,将j指向右孩子
if(j<high&&a[j]<a[j+1])
j++;
if(tmp<a[j]){//如果父节点小于孩子节点,交换位置,指针下移
a[i]=a[j];
i=j;
j=2*i;
}else break;
}
a[i]=tmp;//被筛选值放到最终位置
}
初始堆构造好后,根节点一定是最大的关键字,将其与序列的最后一个元素互换,则无序区元素减少一个,最大元素已经归位。接着对无序区的元素调整构建成堆,如此反复操作,直到完全二叉树只剩一个根为止。
实现堆排序算法如下:
void HeapSort(int a[],int n){
int i,tmp;
//循环构建初始堆
//n/2是第一个父节点,循环依次向根结点方向
for(i=n/2;i>=1;i--){
sift(a,i,n);
}
//进行n-1趟排序
for(i=n;i>=2;i--){
tmp=a[1];
a[1]=a[i];
a[i]=tmp;
sift(a,1,i-1);//筛选a[1]节点,得到i-1个节点的堆
}
}
实例代码
#include <iostream>
using namespace std;
void sift(int a[],int low,int high){
int i=low,j=2*i;//a[j]是a[i]的左孩子
int tmp;
tmp=a[i];
while(j<=high){
//若有孩子较大,将j指向右孩子
if(j<high&&a[j]<a[j+1])
j++;
if(tmp<a[j]){//如果父节点小于孩子节点,交换位置,指针下移
a[i]=a[j];
i=j;
j=2*i;
}else break;
}
a[i]=tmp;//被筛选值放到最终位置
}
void HeapSort(int a[],int n){
int i,tmp;
//循环构建初始堆
//n/2是第一个父节点,循环依次向根结点方向
for(i=n/2;i>=1;i--){
sift(a,i,n);
}
//进行n-1趟排序
for(i=n;i>=2;i--){
tmp=a[1];
a[1]=a[i];
a[i]=tmp;
sift(a,1,i-1);//筛选a[1]节点,得到i-1个节点的堆
}
}
int main()
{
int a[11]={0,0,7,6,8,9,5,3,4,2,1};//这里第一个元素是不使用的
HeapSort(a,10);
for(int i=0;i<10;i++){
cout << a[i] << endl;
}
return 0;
}
算法分析
堆排序的时间复杂度分析比较复杂,这里就把结论记下来。
堆排序最坏时间复杂度为
堆排序的平均性能分析就更困难,研究表明,比较接近于最坏性能。
堆排序只是用了i,j,tmp3个辅助变量,空间复杂度为O(1)
筛选时,相同关键字的相对位置可能会发生变化,所以堆排序是一种不稳定的排序算法。