堆排序
核心要点:
- 通过下沉的方式,自底向上进行建堆 ,可以保证当检测到有父节点的堆有序时,其所有子堆都是满足堆的成立条件。即父节点大于任意两个子节点
- 下沉排序的过程,实质上是在删除最大元素后,堆的自我调整过程。调整的过程中,堆逐渐构成一个有序序列
- 父节点的座标是左子节点的一半,所以开头要减一。
- 由于使用了一个完全二叉树,因此索引为0的位置不能有数
public static void heapSort(int[] arr) {
int N = arr.length - 1;
// 建堆的模式是自下而上进行的,完全二叉树的性质,数组从1开始记录
for (int i = N / 2; i >= 1; i--) {
// 下沉建堆。保证父节点比两个子节点大
sink(arr, i, N);
}
while (N > 1) {
// 将最大的值交换到最后面(删除最大元素),并且数组长度减一。这样下去可以得到最终有序的数组
swap(arr, 1, N--);
// 原堆被打破,从子节点选择一个最大的替换堆顶。对交换后的堆进行下沉再排序
sink(arr, 1, N);
}
}
private static void sink(int[] arr, int a, int N) {
// 需要从上至下进行遍历
while (2 * a <= N) {
// 子节点座标
int j = a * 2;
// 右子节点比左子节点大,选择右子节点, 并且座标要保证不超过N
if (j < N && arr[j + 1] > arr[j]) j++;
// 如果父堆比子堆大,则不需要下沉。因为默认是从下自上建堆的
if (arr[a] > arr[j]) break;
// arr[a] < arr[j] 交换a和j的位置,因为j的序号较大,需要将较大值放到后面
swap(arr, a, j);
// 继续遍历子节点
a = j;
}
}