堆的定义
n个关键字序列:成为堆,当且仅当该序列满足以下性质(简称为堆性质):
(1) 且
(2) 且
满足(1)情况的堆为小根堆,满足第二种情况的为大根堆。下面讨论的是大根堆。
排序思路
堆排序的过程与直接选择排序类似,只是挑选最大或是最小元素的不同,这里采用大根堆,每次挑选最大元素归位。挑选最大元素的方法是将数组中存储的数据看成是一棵完全二叉树,利用完全二叉树中双亲结点和孩子结点之间的内在关系来选择关键字最大元素(为了与二叉树顺序存储结构保持一致,堆排序的数据序列的下标从1开始)
数组和二叉树的对应关系:{6,8,7,9,0,1,3,2,4,5}
堆排序的关键是构建初始堆:初始堆建立是通过循环,从下往上建立的。假设完全二叉树a[n]的某一个结点为a[i],它的左子树、右子树已经是堆,接下来需要将它的左右子节点a[2i + 1]和a[2i+2]进行比较,选择一个最大的,与a[i]比较,如果a[i]较小,将其与最大孩子关键字互换,可能会破坏下一级的堆,所以继续采用上述方法构造下一级的堆,直到完全二叉树的i结点构造成堆为止。
i取(n/2 - 1) ~ 0,反复够利用上诉方法建堆,顺序如图。(这里 n / 2 - 1计算方法:设第一个父节点为x, 如果n - 1为奇数,即x的左节点为最后一个元素,2x + 1 = n - 1, x = n / 2 - 1, 同理证明n - 1为偶数,x = (n - 3) / 2 = (n - 2) / 2)
图中红色数字代表最小包围框中的二叉树调整顺序。
调整堆的算法sift()如下:
public void shift(int[] arr, int low, int high) {
int i = low, j = 2 * i + 1; // arr[j] 为arr[i]的左节点
int temp = arr[i];
while (j <= high) {
// 右节点大于左节点
if (j < high && arr[j] < arr[j + 1]) {
j++;
}
if (temp < arr[j]) {
arr[i] = arr[j];
i = j;
j = 2 * i + 1;
} else {
break;
}
}
arr[i] = temp;
}
初始堆构造好后,根节点一定是最大的关键字,将其与序列的最后一个元素互换,则无序区元素减少一个,最大元素已经归位。接着对无序区的元素调整构建成堆,如此反复操作,直到完全二叉树只剩一个根为止。
实现堆排序算法如下:
public void heapSort(int[] arr) {
int n = arr.length;
for (int i = n / 2 - 1; i >= 0; i--) {
shift(arr, i, n - 1);
}
for (int i = n - 1; i >= 1; i--) {
swap(arr, i, 0);
shift(arr, 0, i - 1);
}
}
实例代码
public class Main {
@Test
public void fun() {
int[] arr = {9,8,5,6,7,4,3,1,2};
heapSort(arr);
for (int num: arr) {
System.out.println(num);
}
}
public void heapSort(int[] arr) {
int n = arr.length;
for (int i = n / 2 - 1; i >= 0; i--) {
shift(arr, i, n - 1);
}
for (int i = n - 1; i >= 1; i--) {
swap(arr, i, 0);
shift(arr, 0, i - 1);
}
}
public void shift(int[] arr, int low, int high) {
int i = low, j = 2 * i + 1; // arr[j] 为arr[i]的左节点
int temp = arr[i];
while (j <= high) {
// 右节点大于左节点
if (j < high && arr[j] < arr[j + 1]) {
j++;
}
if (temp < arr[j]) {
arr[i] = arr[j];
i = j;
j = 2 * i + 1;
} else {
break;
}
}
arr[i] = temp;
}
public void swap(int[] arr, int a, int b) {
int temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
}
算法分析
堆排序的时间复杂度分析比较复杂,这里就把结论记下来。
堆排序最坏时间复杂度为
堆排序的平均性能分析就更困难,研究表明,比较接近于最坏性能。
堆排序只是用了i,j,tmp3个辅助变量,空间复杂度为O(1)
筛选时,相同关键字的相对位置可能会发生变化,所以堆排序是一种不稳定的排序算法。