堆排序是对直接选择排序的一种优化:直接选择排序,在n个键值中选出最小值,至少进行n-1次比较。然而继续在剩余的n-1个键值中选出次小值,要比较n-2次。怎样才能利用前面n-1次的比较所得的信息,来减少以后比较的次数,所以有了堆排序。
堆排序:
设有 n 个元素,欲将其按关键字排序。可以首先将这 n 个元素按关键字建成堆,将堆顶 元素输出,得到 n 个元素中关键字最大(或最小)的元素。然后,再将剩下的 n-1 个元素重 新建成堆,再输出堆顶元素,得到 n 个元素中关键字次大(或次小)的元素。如此反复执行, 直到最后只剩一个元素,则可以得到一个有序序列,这个排序过程称之为堆排序。
堆的特点:
稳定性: 不稳定
使用条件: 在待排序记录较少时不适用,记录数多时很有效
时间复杂度: 运行时间主要消耗在初始建堆和不断“筛选”的过程,所以平均时间为O(nlog2n),最坏情况下时间复杂度也为O(nlog2n)
空间效率:只需要一个辅助空间
时间复杂度:O(n logn)
2个问题值得思考:
对于待排序的初始序列{28 , 26 , 17 , 36 , 20 , 42 , 11 , 53}
1)如何由一个初始序列建成一个堆?
2)如何在输出堆顶元素之后调整剩余元素成为一个堆?
设有一个具有 m 个元素的堆,输出堆顶元素后,剩下 m-1 个元素。具体的调整方法是:
首先,将堆底元素(最后一个元素)送入堆顶,此时堆被破坏,其原因仅是根结点不满足堆 的性质,而根结点的左右子树仍是堆。然后,将根结点与左、右子女中较大(或较小)的进行交换。若与左孩子交换,则左子树堆被破坏,且仅左子树的根结点不满足堆的性质;若与右孩子交换,则右子树堆被破坏,且仅右子树的根结点不满足堆的性质。继续对不满足堆性 质的子树进行上述交换操作,直到叶子结点,则堆被重建。我们称这个自根结点到叶子结点 的调整过程为筛选。
其实这就是一个反复“筛选”的过程:
首先将要排序的所有键值看成一颗完全二叉树的各个结点(这时的完全二叉树并不一定具备堆的特性),
根据完全二叉树性质最后一个非终端节点是第[n/2]个元素,即对于i>[n/2]的结点Ki都没有孩子结点,因此以这样的Ki为根的子树已经是堆,
所以“筛选”只需要从[n/2]开始,逐步把以K[n/2],K[n/2]-1,K[n/2]-2,...,K1为根的子树“筛选”成堆,就完成建堆的过程了。
代码展示:
public void Heap(int[] a) {
int n = a.length;
// 第一步:初始化最小堆
// 思路:
//1. 最后一个非终端节点是第[n/2]个元素,
//2. 即对于i>[n/2]的结点Ki都没有孩子结点,因此以这样的Ki为根的子树已经是堆。
//3. 所以“筛选”只需要从[n/2]开始,逐步把以K[n/2],K[n/2]-1,K[n/2]-2,...,K1为根的子树“筛选”成堆,就完成建堆的过程了。
// 因为数组下标是从0开始的,所以第一个非终端节点的下标识n/2 -1
for (int i = n/2 -1; i >=0; i--) {
//调用建堆的方法
Sift(a, i, n);
}
// 第二步:堆顶和最后一个交换,调整为最大堆
for (int i = n-1; i >=1; i--) {
//将堆顶记录和堆中最后一个记录互换
int temp = a[0];
a[0] = a[i];
a[i] = temp;
// 交换完成之后,调整剩余元素成为一个最大堆
//现在从堆顶开始与左右孩子比较
Sift(a, 0, i);
}
}
/**
* 建立最小堆的过程
* @param a
* @param currentRootNode
* @param size
*/
public void Sift(int[] a, int currentRootNode, int size) {
// 父节点
int i = currentRootNode;
// 父节点的左孩子
int j = 2 * i + 1;
// size-1 :因为数组下标是从0开始的
// 有左孩子的情况下
while (j <= size-1) {
// 如果有右孩子并且左孩子大于右孩子
if ((j < size-1) && a[j] > a[j + 1]) {
// j为左右孩子中的最小
j++;
}
// 父节点值小于左右孩子中的最小值
if (a[i] < a[j]) {
// 跳出这个循环,判断下一个节点
break;
} else {
// 父节点与最大子节点交换位置
int temp = a[i];
a[i] = a[j];
a[j] = temp;
// 交换完位置之后,再进行与下面孩子的比较(这里需要特别注意一下)
i = j;
j = 2 * i+1;
}
}
}
测试堆排序:
/**
* 堆排序测试
**/
@Test
public void testHeapSort() {
int[] a = new int[]{3, 1, 4, 6, 5, 9, 7, 2, 8, 0};
Heap(a);
System.out.println("--------排序好的堆--------");
for (Integer yuansu : a) {
System.out.println(yuansu);
}
}