在此排序过程中,我并没有建立一个真正的二叉堆,而是用一个数组来模拟堆,每次把通过回溯找到的最大或最小元素与最后一个元素交换,最后形成一个顺序的数组。
首先我们来看一下小元素下移与大孩子上移的算法,与二叉堆的建立基本相同。
#define leftchild(i) (2*i+1)
void PerDown(int a[],int i,int n)//建立一个最大堆的过程,小的元素下移
{
int child;
int tmp;
for(tmp=a[i];leftchild(i)<n;i=child)
{
child=leftchild(i);
if(child!=n-1&&a[child+1]>a[child])
child++;
if(tmp<a[child])
a[i]=a[child];
else
break;
}
a[i]=tmp;
}
i代表当前的位置,我们还定义了计算左孩子的直接过程,第一个if找出了i节点的大孩子,第二个if把让大孩子上移,保证了该节点比两个孩子都大,最后是该节点应该放置 的位置。
接下来我们来看一下建堆的过程,代码上来:
void HeapSort(int a[],int n)//堆排序实现的函数
{
int i;
for(i=n/2;i>=0;i--)//从中间往上建立一个堆,会检查孩子的大小
{
PerDown(a,i,n);
}
for(i=n-1;i>0;i--)
{
swap(a[0],a[i]);//交换首位的位置,交换之后最后一个元素是最大的
PerDown(a,0,i);//只给前n个数下移
}
}
从所有节点的一半位置处往上建立就可以了,因为叶子节点没有孩子。每次建完都交换首尾的位置,然后再下移即可,最后形成一个完整的有序数组。
总结:堆排序是一个非常稳定的算法,对于n个互异的随机排列的数组,所用的平均比较次数为2nlogn-O(nloglogn),约为2nlogn-O(n)。