2020-11-24 排序算法二(堆排序)

在前面,我们讲了二叉堆的实现,堆顶元素始终是最大或最小的,那么堆排序是怎么一回事呢?

  1. 根据数列生成一个最大堆(需要从小到大排序的时候)或最小堆;
  2. 交换堆顶和最后一个元素(假删除,自我调节中不需要处理交换到最后的栈顶元素),进行自我调节(downAdjust / upAdjust);
  3. 循环数列每个元素执行第2步;
    最终形成了一个层序遍历为从小到大(从大到小)数列的二叉树。
构建堆

把一个无序数列构建成一个二叉堆,最大最小而异,本质都是将其所有非叶子节点依次下沉,为什么这样就可以构建一个二叉堆呢?

二叉堆的定义,父元素的值始终大于等于或者小于等于子节点的值,那么我们只需要将所有父节点都移动至满足该条件即可,所以所有的父节点都是非叶子节点。

我们在构建堆的时候,该如何找到所有非叶子节点呢?

我们都知道查找一个右子节点的父节点的索引为parentIndex = (childIndex - 2)/ 2,那么索引值最大的非叶子节点是多少?很明显,最后一个节点的父节点就是,即maxNotLeafNodeIndex = Math.floor((list.length - 1) / 2)。此时我们只需要从这个节点往前,遍历剩余的非叶子节点,将其依次下沉调整,即可得到一个二叉堆。

function buildHeap(list) {
  const maxNotLeafNodeIndex = Math.floor((list.length - 1) / 2);
  for (let i = maxNotLeafNodeIndex; i >= 0; i--) {
    downAdjust(list, i, list.length);
  }
  return list;
}
通过构建最大堆来排序

构建完堆以后,执行上面排序步骤的第2步第3步:

function downAdjust(list, targetIndex, realLength) {
  let parentIndex = targetIndex;
  const target = list[parentIndex];
  let childIndex = Math.floor(2 * parentIndex + 1);
  while (childIndex <= realLength - 1) {
    if (childIndex + 1 <= realLength - 1 && list[childIndex+1] > list[childIndex]) {
      childIndex++;
    }
    if (list[childIndex] <= target) {
      break;
    }
    list[parentIndex] = list[childIndex];
    parentIndex = childIndex;
    childIndex = Math.floor(2 * parentIndex + 1);
  }
  list[parentIndex] = target;
}
function headSort(list) {
  // 生成最大堆
  const maxNotLeafNodeIndex = Math.floor((list.length - 1) / 2);
  for (let i = maxNotLeafNodeIndex; i >= 0; i--) {
    downAdjust(list, i, list.length);
  }
  // 遍历
  for (let i = 0; i < list.length; i++) {
    const max = list[0];
    // 最大的元素到数列末尾,不再参与堆的自我调整过程
    const usefulLen = list.length - 1 - i;
    list[0] = list[usefulLen];
    list[usefulLen] = max;
    downAdjust(list, 0, usefulLen);
  }
  return list;
}
堆排序的总结
  • 找到所有非叶子节点,构建二叉堆;
  • 从堆顶开始遍历,放置到堆底,再次进行自我调整,形成有序数列;
  • 时间复杂度稳定为O(nlogn),空间复杂度为O(1),不需要任何多余的空间来存储额外值。

你可能感兴趣的:(2020-11-24 排序算法二(堆排序))