1.基本思想
利用大顶堆(最大数在根部的堆,与Java中的堆(小顶堆)正好相反)。将待排序的元素构造成为一个大顶堆,此时最大数在根部,将最大数与堆数组的末尾元素交换,那么最大元素就在数组尾部。将除了尾部的元素重新构造为一个大顶堆,得到了次大值,重复上述过程就得到了一个有序数组
2.算法实现
算法基于二叉堆,不同的是二叉堆的0角标不存放元素,而在这里是存放根部的
public static <AnyType extends Comparable<? super AnyType>> void heapsort(AnyType[] a)
{
//将数组构建成大顶堆:除树叶之外的元素都进行一次下沉
//由于0角标的存在,左子树为2*i+1,右子树为2*i+2
for(int i = a.length / 2 - 1; i >= 0; i--)
percolateDown(a,i,a.length);
//将根与堆尾互换元素,堆中最大元素置于数组尾部,即根与堆尾互换位置,新树不包含堆尾。再将新根下沉
for(int i = a.length - 1; i > 0; i--)//堆尾的角标不断减小
{
swapReferences(a, 0, i);//换位
percolateDown(a, 0, i);//下沉
}
}
//下沉方法
private static <AnyType extends Comparable<? super AnyType>> void percolateDown(AnyType[] a, int i,int n)
{
int child;
AnyType tmp;
//寻找两个子树中最大的子树并与其父亲比较大小,若父亲小于子树,则子树元素赋给父亲,即下沉一次,之后继续下沉
for(tmp = a[i]; leftChild(i) < n; i = child )
{
child = leftChild(i);
if(child != n - 1 && a[child].compareTo(a[child + 1]) < 0)//小顶堆为大于号
child++;
if(tmp.compareTo(a[child]) < 0)
a[i] = a[child];
else
break;
}
a[i] = tmp;
}
//交换元素方法
private static <AnyType extends Comparable<? super AnyType>> void swapReferences(AnyType[] a, int index1, int index2)
{
AnyType tmp;
tmp = a[index1];
a[index1] = a[index2];
a[index2] = tmp;
}
//获得左子树角标的方法
private static int leftChild(int i)
{
return 2 * i + 1;
}
3.算法分析
- 最好、最坏、平均时间复杂度均为O(n*logn)
- 构建堆时,对于每个非终端节点来说,最多进行两次比较和互换操作,因此构建堆的时间复杂度为O(n)
- 重建堆时,第i次重建现需要O(logi)的时间,并需要n-1次的取堆顶,重建堆的时间复杂度为O(n*logn)
- 空间复杂度上,只有一个用来交换的暂存单元,因此空间复杂度为O(1)
- 不需要额外数组
- 比较与交换是跳跃的,因此是一种不稳定的排序方法
- 由于初始构建堆所需的比较次数较多,因此不适合小序列排序