一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第21天,点击查看活动详情。
基础原理
首先我们尝试让整个arr变成大根堆,首先处理3,然后heapinsert5,然后heapinsert9 ....
调整完了以后arr[0] = largest = 9 heapSize增加到7
其实我们在arr[0]处保证得到了数组的最大值,接着我们将其和数组最后一位交互,并heapSize--,这样数组最后一位值就确定了并且和堆断开了联系。
后面的操作就是从交换后的arr[0] = 0处开始进行heapify让剩余数字重新生成大根堆,接着arr[0]会得到次大值,接着和堆最后一位交换(length - 2)....
简单来说先heapinsert构成初始大根堆,然后再进行heapify不断更新大根堆,在此期间不断获取大值并保存
代码实现
这里核心代码包括排序过程 - heapinsert - heapify:
public static void heapSort(int[] arr){
if(arr == null || arr.length < 2){
return;
}
// 构造初始大根堆
for(int i = 0; i < arr.length; i++){ // O(N)
heapInsert(arr, i); // O(logN)
}
int heapSize = arr.length;
swap(arr, 0 , arr.length - 1);
// 堆化维护大根堆并交换大值
while(heapSize > 0){ // O(N)
heapify(arr, 0 , heapSize); // O(logN)
swap(arr, 0 , --heapSize); // O(1)
}
}
// ...堆插入和堆化过程之前章节已经写了
最终时间复杂度是O(N*logN)
额外空间复杂度是O(1) -> heapInsert没有申请额外变量, heapify里面也就几个变量
也没有递归空间,没有额外栈空间
扩展 - 优化基于已有数组生成大根堆过程
之前的heapinsert其实是能够很好处理用户一个数一个数给你的诉求。如果是直接给一个数组其实有优化的算法可以更好解决。
此时情况相当于用户直接给了你一个完全二叉树但不是堆,那我们其实就可以从数组最后一个数字开始作heapify,下图中最下层8个不需要操作,接着一轮保证倒数2层4棵子树都为大顶堆,然后倒数第三层2棵子树都为大顶堆...就是基于heapify操作从底向上操作即可
这样其实时间复杂度是要更好的,他的复杂度:
其实是可以完全不用heapinsert操作来实现堆排序,并且还会快一点(构建初始堆过程):