堆排序的JavaScript实现

思想

把数组当做二叉树来排序:

  • 索引0是树的根节点;
  • 除根节点外,索引为N的节点的父节点索引是(N-1)/2;
  • 索引为N的节点的左子节点索引是 2*N+1;
  • 索引为N的节点的右子节点索引是 2*N+2;

关于二叉树相关tips:

  • 满二叉树:深度为k且有2^k-1个节点的二叉树
  • 完全二叉树:有n个节点的二叉树,当且仅当其每个节点按从上到下、从左至右的顺序进行编号,其编号与满二叉树中1至n的节点编号一一对应
  • 第k层的最大节点数: 2^(k-1)
  • 有n个节点的完全二叉树中最后一个有子节点的节点的序号:Math.floor(n/2-1)

代码

function heapSort(arr) {
  let heapSize = arr.length;
  buildHeap(arr);//构造一个所有节点都满足arr[parent[i]] > arr[i]的堆结构数组,这样就把值最大的那个节点换到了根节点
  while(heapSize > 1) { //*1

    //在当前树中,交换位于根节点的最大值和最后一个节点的值,这样就把最大值排在了最后一个节点,这样就排好了最大值
    const temp = arr[0];
    arr[0]=arr[heapSize-1];
    arr[heapSize-1] = temp;

    heapSize--;//当前树中最后一个节点已经排好了值,故后面就不用再考虑这个节点,故新的树的大小减一

    if (heapSize>1) {
      heapify(arr, heapSize, 0);//上面的交换操作产生了新的根节点,新的根节点只是通过跟最后一个节点交换得到的值,故新的根节点不满足条件arr[parent[i]]<arr[i],所以要对根节点再次进行h
    }
  }
}

/**
 * @description 构造一个所有节点都满足arr[parent[i]] > arr[i]的堆结构数组
 * @param {Array} arr 待排序数组
 */
function buildHeap(arr) {
  const heapSize = arr.length;
  const firstHeapifyIndex = Math.floor(heapSize/2-1);//从树的倒数第二层的最后一个有子节点的节点(对于满二叉树就是倒数第二层的最后一个节点)开始进行heapify处理。Math.floor(heapSize/2-1)就是这个最后一个有子节点的节点索引。
  for (let i=firstHeapifyIndex; i >= 0; i--) {//从0到firstHeapifyIndex都要进行heapify处理,才能把最大的那个节点换到根节点
    heapify(arr, heapSize, i);
  }
}

/**
 * @description 以数组arr的前heapSize个节点为树,对其中索引为i的节点向子节点进行替换,直到满足从i往下的子节点都有arr[parent[i]]>=arr[i]
 * @param {*} arr TYPE Array  待排序的数组
 * @param {*} heapSize TYPE Number 待排序的数组中要作为当前树处理的从前往后数的节点个数,即待排序数组中前heapSize个点是要作为树来处理
 * @param {*} i TYPE Number arr数组中、heapSize长度的树中的当前要进行往子节点替换的节点的索引
 */
function heapify(arr, heapSize, i) {
  const leftIndex = i * 2 + 1;//索引i的节点的左子节点索引
  const rightIndex = i * 2 + 2;//索引i的节点的右子节点索引
  let biggestValueIndex = i;
  if (leftIndex < heapSize && arr[leftIndex] > arr[biggestValueIndex]) {
    //节点的最大index为heapSize-1
    //注意:这两次比较要跟arr[biggestValueIndex]比较,不能跟arr[i]比较,因为biggestValueIndex是会在左右i之间更新的
    biggestValueIndex = leftIndex; //如果左子节点的值大于biggestValueIndex的值(此时就是根节点的值),那么更新biggestValueIndex为左子节点索引
  }
  if (rightIndex < heapSize && arr[rightIndex] > arr[biggestValueIndex]) {
    biggestValueIndex = rightIndex;//如果右子节点的值大于biggestValueIndex的值(此时可能是根节点的值,也可能是左子节点的值),那么更新biggestValueIndex为右子节点索引
  }
  if (biggestValueIndex !== i) { //如果biggestValueIndex是左子节点索引或右子节点索引,那么交换根节点与biggestValueIndex节点的值
    const temp = arr[i];
    arr[i] = arr[biggestValueIndex];
    arr[biggestValueIndex] = temp;

    //交换后,被交换的那个子节点(左子节点或右子节点)往下可能就不再满足[parent[i]]>=arr[i],所以要继续对biggestValueIndex进行heaify处理,即将biggestValueIndex可能需要和子节点进行值交换,直到树的这个分支到叶子节点都满足arr[parent[i]]>=arr[i]
    heapify(arr, heapSize, biggestValueIndex);//要

  }
}

工作过程

待画图

性能分析

  • 时间复杂度:最好、平均、最坏都是 O(nlogn)
  • 空间复杂度: O(1),不稳定

延伸:如果数组数量大于10,只找出最大的10个值呢?

上述代码的*1行换成:

while(heapSize > arr.length - 10)

参考资料

-《学习JavaScript数据结构和算法》10.1.6
-《数据结构(C语言版)》9.4.2

posted on 2018-06-24 17:08  阿琛  阅读(903)  评论(0编辑  收藏  举报