二叉堆的特征:
1. 最大堆的堆顶是整个堆中最大的元素
2. 最小堆的堆顶是整个堆中最小的元素
以最大堆为例,如果删除一个最大的堆顶(并不是真正意义上的删除,而是和末尾节点交换位置),经过自我调整,第二大元素就会被交换上来,成为最大堆的新堆顶
由于二叉堆的这个特性,每一次删除旧的堆顶,调整后的新堆顶都是大小仅次于旧堆顶的节点,那么只要反复删除堆顶,反复调整二叉堆,所得到的的集合就会成为一个有序的集合,过程如下:
删除节点 9,节点 8 成为新的堆顶
删除节点 8,节点 7 成为新的堆顶
删除节点 7,节点 6 成为新的堆顶
删除节点 6,节点 5 成为新的堆顶
删除节点 5,节点 4 成为新的堆顶
删除节点 4,节点 3 成为新的堆顶
删除节点 3,节点 2 成为新的堆顶
到此为止,原本的最大二叉堆已经变成了一个从小到大的有序集合,
由此,可以归纳出堆排序算法的步骤:
1. 把无序数组构建成为二叉堆,需要升序,则构建最大二叉堆,需要降序则构建最小二叉堆
2. 循环删除堆顶元素,替换到二叉堆的末尾,调整堆产生新的堆顶
代码
/**
* @param {Array<number>} arr 待调整的堆
* @param {number} parentIndex 要下沉的父节点
* @param {number} length 堆的有效大小
* @description 下沉调整 最大堆
*/
function downAdjust(arr: Array<number>, parentIndex: number, length:number) {
// temp 保存父节点的值,用作最后赋值
let temp = arr[parentIndex];
// 左子节点下标
let childrenIndex = parentIndex * 2 + 1;
while(childrenIndex < length) {
// 如果存在右节点,且右节点比左节点大 则定位到右节点
if(childrenIndex + 1 < length && arr[childrenIndex + 1] > arr[childrenIndex]) {
childrenIndex = childrenIndex + 1;
}
// 如果父节点大于等于最大子节点,则直接break
if(temp >= arr[childrenIndex]) {
break
}
// 单向赋值
arr[parentIndex] = arr[childrenIndex];
parentIndex = childrenIndex
childrenIndex = childrenIndex * 2 + 1
}
arr[parentIndex] = temp;
}
/**
* @param {Array<number>} arr 待调整的堆
* @description 堆排序 升序
*/
function heapSort(arr:Array<number>) {
// 1. 把无序数组构建为最大堆
for (let index = arr.length - 2; index >= 0; index--) {
downAdjust(arr, index, arr.length - 1)
}
console.log('最大堆: %j', arr)
// 2. 循环删除堆顶元素,移动到数组尾部,调整堆,产生新的堆顶
for (let index = arr.length - 1; index >= 0; index--) {
// 最后一个元素和 堆顶交换
let temp = arr[0];
arr[0] = arr[index];
arr[index] = temp;
// 下沉调整最大堆
downAdjust(arr, 0, index)
// index 的值 为堆的有效长度 这里很巧妙
}
}
function main() {
let arr = [1, 3, 2, 6, 5, 7, 8, 9, 10, 0];
heapSort(arr);
console.log(arr)
}
main()
小结
时间复杂度
二叉堆的节点下沉调整(downAdjust方法)是堆排序算法的基础,它的时间复杂度为O(logn)
第一步把无序数组构建为二叉堆,这一步时间复杂度为O(n)
第二步进行 n-1 次循环,每次循环调用下沉方法,所以步骤 2 的规模为 (n-1)*logn
两个步骤是并列关系,所以整体时间复杂度为O(nlogn)
快速排序和堆排序对比
相同点:
堆排序和快速排序的平均时间复杂度都是O(nlogn),并且都是不稳定排序
不同点:
1. 快速排序的最坏时间复杂度为O(n2),而堆排序则稳定在O(nlogn)
2. 快速排序的递归和非递归实现 空间复杂度 都是O(logn),而堆排序的空间复杂度为O(1)
摘要总结自: 漫画算法 小灰的算法之旅