具体的演示过程可以参考:https://www.cnblogs.com/chengxiao/p/6129630.html
以大堆顶为例,有:arr[i] >= arr[2i+1] && arr[i] >= arr[2i+2]
// 最大堆排序,可以得到递增序列
public void heapSort(int[] a) {
if (a == null || a.length == 0) {
return;
}
// 从后往前遍历所有元素,构建出最大堆
// 从前往后遍历是为了在一个已经整理好的堆上插入新的元素
// 这里使用原地算法,当插入 a[i] 时, a[i+1,maxLen] 已经是一个整理好的堆了
// 即 a[i+1,maxLen] 不能再被乱动
// 所以遍历元素时从前往后
for (int i = a.length - 1; i >= 0; i--) {
buildBigHeap(i, a, a.length);
}
// 有了最大堆之后,每次都把堆顶的与当前末尾元素交换,则可以得到升序排序的最终序列
for (int i = a.length - 1; i > 0; i--) {
Common.sweap(a, 0, i);// sweap() 方法用于交换数组中的两个元素
// 排除掉当前末尾的元素,重新构建最大堆
buildBigHeap(0, a, i - 1);
}
}
// 构建最大堆
private void buildBigHeap(int curIndex, int[] a, int curLen) {
int childLeft = curIndex * 2 + 1;
int chileRight = childLeft + 1;
int tmpMaxIndex = childLeft;
if (childLeft >= curLen) return;
// 找到两个子节点中最大的那一个
if (chileRight < curLen && a[tmpMaxIndex] < a[chileRight]) {
tmpMaxIndex = chileRight;
}
if (a[tmpMaxIndex] > a[curIndex]) {
Common.sweap(a, tmpMaxIndex, curIndex);
// 把当前父节点设置成包含其子节点中最大的那一个后,
// 被设置的那个子节点也需要跟着调整
// 因为该子节点也会是下一级的一个父节点
// 即此时包含该子节点的子树会受到影响
buildBigHeap(tmpMaxIndex, a, curLen);
}
}
堆排序也是一种不稳定的排序。
因为使用的原地算法,所以空间复杂度为 O(1)
对于时间复杂度的话,先说结论,它的最坏,最好,平均时间复杂度均为 O(n log n)
对于时间复杂度的,主要分为两部分,一个是建堆的过程,另一个是每次都把堆顶的与当前元素末尾交换,则可以得到升序排序的最终序列的过程
(1)堆排序中建堆过程时间复杂度 O(n) 的由来,参考自:https://www.zhihu.com/question/20729324
(2)在交换并重建堆的过程中,重建堆一共需要 (n-1) 次循环(n 为需要排序的元素个数):
第一次循环中 ,将堆顶的元素与最后一个交换,之后重建堆的时候就只有 (n-1) 个元素了,此时有比较次数 log (n-1);
第二次循环中 ,将堆顶的元素与倒数第二个交换,之后重建堆的时候就只有 (n-2) 个元素了,此时有比较次数 log (n-2);
因此,总共有 log (n-1) + log (n-2) + ... + log 2 = log ((n-1)*(n-2)*...*2) = log ((n-1)!) ≈ n log n
因此总的为 O(n + n log n),所以堆排序时间复杂度一般认为就是 O(n log n) 级。