# 堆排序

将数组片段在线性的时间内调整成最大(小)堆,取出堆顶的元素和数组无序片段的最后一个元素进行交换,有序片段规模增加 1,无序片段规模减少 1,再将剩余的无序片段元素调整成最大(小)堆,重复这个操作,直到无序片段为空则完成排序。

# 排序过程

# 算法实现

/**
 * 将长度为length的数组片段以p为根节点构建最大堆
 * @param {Array<number>} arr 需要进行排序的数组
 * @param {number} p 根节点
 * @param {number} length 数组片段的长度
 */
function percDown(arr, p, length) {
  let temp = arr[p];
  let child, parent;
  // 从p节点开始,如果parent*2等于length的话,说明堆已经越界,无需进行循环
  for (parent = p; parent * 2 < length; parent = child) {
    // 找到左儿子节点所在的索引
    child = parent * 2;
    // 右儿子存在,并且右儿子比左儿子大,则取右儿子
    if (child + 1 < length && arr[child] < arr[child + 1]) {
      child++;
    }
    // 如果待插入的值比当前这个位置大或者相等,则说明这个位置就是可以插入的位置,不能再继续下滤了,因此退出循环
    if (temp >= arr[child]) {
      break;
    } else {
      // 把大的值向上提
      arr[parent] = arr[child];
    }
    // 节点下滤
  }
  // 把元素放在合适的位置
  arr[parent] = temp;
}

/**
 * 对数组进行堆排序
 * @param {Array<number>} arr 需要进行排序的数组
 */
function heapSort(arr) {
  // 因为在没有哨兵时,对于父节点为i的节点,其左右儿子分别是 2i+1, 2i+2,那我们可以根据最后一个元素算出,最后一个元素的父节点是 Math.floor(arr.length / 2)
  // 从最后一个元素的父元素开始,在线性的时间内将数组调整成最大堆,
  for (let i = Math.floor(arr.length / 2); i >= 0; i--) {
    percDown(arr, i, arr.length);
  }
  for (let i = arr.length - 1; i >= 0; i--) {
    // 取出堆顶的第一个元素
    let temp = arr[0];
    // 将无序片段最后一个元素交换到堆顶
    arr[0] = arr[i];
    arr[i] = temp;
    // 继续将长度为i的元素片段,以0为根节点,调整成最大堆
    percDown(arr, 0, i);
    // 最后无序片段的规模递减
  }
}

# 复杂度与稳定性

堆排序是不稳定的排序算法;

定理: 堆排序处理 N 个不同元素的随机排列的平均比较次数是 2N*logN-O(N*log(logN))

堆排序的复杂度可以写成 O(N*logN),而且比这个复杂度要比 O(N*logN)略好一些。

平均算法复杂度:O(N*logN),无额外的空间复杂度