堆排序
# 堆排序
时间复杂度: O(NlogN)
空间复杂度: O(logN)
# 建堆思想
堆是一棵完全二叉树,它可以使用数组存储,并且堆的最值存储在根节点。
所以我们可以每次取堆的根结点与堆的最后一个节点交换,
此时最值放入了有效序列的最后一位,并且有效序列减 1,有效堆依然保持完全二叉树的结构。
然后堆化,成为新的顶堆,重复此操作,直到有效堆的长度为 0,排序完成。
# 实现
完整步骤为:
- 将待排序序列构成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点
- 将其与末尾元素进行交换,此时末尾元素就是最大值
- 然后将剩余 n-1 个元素重新构成一个堆,就会得到 n 个元素的次小值,如此反复执行,便能得到一个有序序列
function heap_sort(arr) {
let len = arr.length;
function swap(i, j) {
let temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
// 将 start 结点以下的堆整理为大顶堆
function max_heapify(start, end) {
// 当前父节点
let dad = start;
let son = dad * 2 + 1;
// 如果交换前父节点大于子节点,退出
if (son >= end) return;
// 找到两个孩子中较大的一个,再与父节点比较
if (son + 1 < end && arr[son] < arr[son + 1]) {
son++;
}
// 如果父节点小于等于子节点:交换
if (arr[dad] <= arr[son]) {
swap(dad, son);
max_heapify(son, end);
}
}
// 初始化大顶堆
// 从第一个非叶子结点开始,对每一个非叶子结点 heapify
for (let i = Math.floor(len / 2) - 1; i >= 0; i--) {
max_heapify(i, len);
}
// 排序,每一次 for 循环找出一个当前最大值,数组长度减一
for (let j = len - 1; j > 0; j--) {
// 根节点与最后一个节点交换
swap(0, j);
// 从根节点开始调整,并且最后一个结点已经为当前最大值,不需要再参与比较,所以第三个参数为 j,即比较到最后一个结点前一个即可
max_heapify(0, j);
}
return arr;
}
console.log(heap_sort([25, 76, 34, 232, 6, 456, 221])); // [6, 25, 34, 76, 221, 232, 456]
# 复杂性对比
名称 | 最好 | 平均 | 最坏 | 内存 | 稳定性 |
---|---|---|---|---|---|
归并排序 | nlog(n) | nlog(n) | nlog(n) | n | Yes |
快速排序 | nlog(n) | nlog(n) | n2 | log(n) | No |
希尔排序 | nlog(n) | 取决于差距序列 | n(log(n))2 | 1 | No |
堆排序 | nlog(n) | nlog(n) | nlog(n) | 1 | No |