堆排序是使用二叉堆实现的优先队列来进行排序的。
先介绍几个概念:
第一个是优先队列,优先队列是一种数据结构,它支持两种操作:删除最大元素和插入元素。
第二个是二叉堆,二叉堆是一种数据结构,它能够很好的实现优先队列的基本操作。它使用一个数组来保存数据,在这个数组中,每个元素都要保证大于等于另两个特定位置的元素。相应的,这些位置的元素又至少要小于等于数组中的另两个元素,以此类推。
如果将所有元素画成一棵二叉树,将每个较大元素和两个较小元素用边连接就可以看出这种结构。
一般使用完全二叉树来表示。
下面就进入正题,给出完整代码:
package asen.yang; public class heapSort { public static void main(String[] args) { // TODO Auto-generated method stub //数组的第一个元素(索引0位置)不使用,因此赋值为MIN_VALUE。 int[] a = { Integer.MIN_VALUE, 5, 1, 4, 8, 3, 9, 0, 2, 7, 6 }; sort(a);//进行排序 //打印输出,从下标1开始打印 for (int i = 1; i < a.length; i++) { System.out.print(a[i] + " "); } } //比较数组中两个索引的元素的大小,如果i<j返回true,否则返回false private static boolean less(int[] pq, int i, int j) { return pq[i] < pq[j]; } //交换数组中两个索引的元素的值 private static void exch(int[] pq, int i, int j) { int t = pq[i]; pq[i] = pq[j]; pq[j] = t; } //上浮操作 private static void swim(int[] pq, int k) { while (k > 1 && less(pq, k / 2, k)) { exch(pq, k / 2, k); k = k / 2; } } //下沉操作 private static void sink(int[] pq, int k, int N) { while (2 * k <= N) { //k的两个子结点是2k和2k+1, int j = 2 * k; if (j < N && less(pq, j, j + 1)) { //这里判断2k与2k+1的大小,选择其中较大的元素 j++; } //如果k(父)节点大于等于j(子)结点,则说明已经调整为有序的状态 if (!less(pq, k, j)) { break; } //否则交换k(父)结点和j(子)结点,使之有序 exch(pq, k, j); //交换完毕,另j(子)结点成为新的比较对象 k = j; } } public static void sort(int[] a) { //数组可用长度为实际长度-1 int N = a.length - 1; //构建堆 for (int k = N / 2; k >= 1; k--) { sink(a, k, N); } //堆排序 while (N > 1) { exch(a, 1, N--); sink(a, 1, N); } } }
其中关键的是sink方法,这个方法的作用是在堆有序的状态下,因为某个结点变得比它的两个子结点或是其中之一更小了而打破了,可以通过将它和它的两个子结点中的较大者交换来恢复堆有序。
交换可能会在子结点处继续打破堆的有序状态,因此需要不断用相同的方式将其恢复,将结点向下移动直到它的子结点都比它小或是达到了堆的底部。
最后的sort方法实现了排序的整体流程。
这里是从右至左用sink函数构造子堆。开始时只需要扫描数组中的一半元素,因为可以跳过大小为1的子堆。最后在位置1上调用sink方法,扫描结束。
接下来的while循环将最大的元素a[1]和a[N]交换并修复了堆,如此重复直到堆变空。
最后打印的结果为:
与预期是一致的。