继上一篇实现基于堆的优先队列后,这次将利用上一次完成的基于堆的能够重复删除最大元素操作的优先队列来实现一种经典而优雅的排序算法,称之为堆排序。
堆排序可分为两个阶段:
1.构建堆:在堆的构建过程中,我们将原始数组重新组织安排进一个堆中;
2.下沉排序:从堆中按递减顺序取出所有元素并得到排序结果
具体的思想在下面的代码中有较为详细的注释:
/**
*
* @author seabear
*
*/
public class HeapSort {
/**
* 1.构造大根堆:与上一篇基于堆的优先队列相似,将原始数组重新组织成一个基于堆的拥有重复删除最大元素操作的优先队列;
* 2.下沉排序:根据构造出来的大根堆,很容易将堆中的最大元素删除,然后放入堆缩小后数组中空出的位置。
* @param a
*/
public static void sort(Comparable[] a)
{
int len = a.length-1;
//构造大根堆
//跳过只有一个结点的堆即大小为1的堆,从数组的中间开始扫描,调用sink()方法,层层递减,最后在1位置上调用sink()方法后结束。
//此次扫描目的是构造一个堆有序的数组并使最大元素位于数组的开头(次大的元素在附近)
for(int k = len / 2; k >= 1; k--)
{
sink(a,k,len);
show(a);
}
System.out.println("下沉开始");
//下沉排序
//1.每次排序都先将最大的元素与最后一个元素交换位置,接着缩小数组,对除去最后一个元素的堆进行下沉排序
//2.对缩小后的数组进行下沉排序,若数组长度大于1,则跳转到第一步继续执行
while(len > 1)
{
exch(a,1,len--);
sink(a,1,len);
show(a);
}
}
//下沉排序
private static void sink(Comparable[] a,int i,int len)
{
while(i*2 <= len)
{
int j = i * 2;
if(j < len && less(a[j],a[j+1]))
{
j++;
}
if(!less(a[i],a[j]))
{
break;
}
exch(a,i,j);
i = j;
}
}
private static boolean less(Comparable v,Comparable w)
{
return v.compareTo(w) < 0;
}
private static void exch(Comparable[] v,int i, int j)
{
Comparable temp = v[i];
v[i] = v[j];
v[j] = temp;
}
public static void show(Comparable[] a)
{
for(int i = 1; i < a.length; i++)
{
System.out.print(a[i] + " ");
}
System.out.println();
}
public static void main(String[] args)
{
int N = 12;
Integer[] a = new Integer[N];
for(int i = 1; i <= N-1; i++)
{
a[i] = (int)(Math.random() * 10 + 1);
}
show(a);
sort(a);
show(a);
}
}
堆排序在排序复杂性的研究中有着重要的地位,因为它是我们所知的唯一能够同时最优地利用空间和时间的方法,即使在最坏的情况下,它也能保证使用~2NlgN次比较和恒定的额外空间。常用于嵌入式系统或低成本的移动设备中(空间十分紧缺的系统);但现代系统的许多应用很少使用它,,因为它无法利用缓存。数组元素很少和相邻的其他元素进行比较,因此缓存未命中的次数要远高于大多数比较都在相邻元素间进行的算法,例如快速排序、归并排序,甚至是希尔排序。另一方面,用堆实现的优先队列在现代应用程序中越来越重要,因为它能在插入操作和删除最大元素操作混合的动态场景中保证对数级别的运行时间。