• 技术文章 >web前端 >js教程

    JS实现堆排序

    不言不言2018-07-07 17:50:38原创1143
    这篇文章主要介绍了关于JS实现堆排序,有着一定的参考价值,现在分享给大家,有需要的朋友可以参考一下

    堆的预备知识


    3942070513-5b3d8014d9ecd_articlex[1].png

    2625369032-5b3d80234ce55_articlex[1].png

    对于结点 i ,其子结点为 2i+1 与 2i+2 。

    堆排序算法

    335703288-5b3d8059a914f_articlex[1].png

    现在需要对如上二叉树做升序排序,总共分为三步:

    1. 将初始二叉树转化为大顶堆(heapify),此时根结点为最大值,将其与最后一个结点交换。

    2. 除开最后一个结点,将其余节点组成的新堆转化为大顶堆,此时根结点为次最大值,将其与最后一个结点交换。

    3. 重复步骤2,直到堆中元素个数为1(或其对应数组的长度为1),排序完成。

    下面详细图解这个过程:

    步骤1:

    初始化大顶堆,首先选取最后一个非叶子结点(我们只需要调整父节点和孩子节点之间的大小关系,叶子结点之间的大小关系无需调整)。设数组为arr,则第一个非叶子结点的下标为:i = Math.floor(arr.length/2 - 1) = 1,也就是数字4,如图中虚线框,找到三个数字的最大值,与父节点交换。

    905275246-5b3d80ca1f8cf_articlex[1].png

    然后,下标 i 依次减1(即从第一个非叶子结点开始,从右至左,从下至上遍历所有非叶子节点)。后面的每一次调整都是如此:找到父子结点中的最大值,做交换。

    1693856705-5b3d80fe79483_articlex[1].png

    这一步中数字6、1交换后,数字[1,5,4]组成的堆顺序不对,需要执行一步调整。因此需要注意,每一次对一个非叶子结点做调整后,都要观察是否会影响子堆顺序!

    347386342-5b3d81326eb7c_articlex[1].png

    这次调整后,根节点为最大值,形成了一个大顶堆,将根节点与最后一个结点交换。

    步骤2:

    除开当前最后一个结点6(即最大值),将其余结点[4,5,3,1]组成新堆转化为大顶堆(注意观察,此时根节点以外的其他结点,都满足大顶堆的特征,所以可以从根节点4开始调整,即找到4应该处于的位置即可)。


    612648797-5b3d815fb686f_articlex[1].png

    275482456-5b3d81831d70b_articlex[1].png

    步骤3:

    接下来反复执行步骤2,直到堆中元素个数为1:

    2047796199-5b3d819a78a94_articlex[1].png

    1677262690-5b3d81a66a490_articlex[1].png

    2335279563-5b3d81a91c32a_articlex[1].png

    堆中元素个数为1, 排序完成。

    JavaScript实现

    // 交换两个节点
    function swap(A, i, j) {
      let temp = A[i];
      A[i] = A[j];
      A[j] = temp; 
    }
    
    // 将 i 结点以下的堆整理为大顶堆,注意这一步实现的基础实际上是:
    // 假设 结点 i 以下的子堆已经是一个大顶堆,adjustheap 函数实现的
    // 功能是实际上是:找到 结点 i 在包括结点 i 的堆中的正确位置。后面
    // 将写一个 for 循环,从第一个非叶子结点开始,对每一个非叶子结点
    // 都执行 adjustheap 操作,所以就满足了结点 i 以下的子堆已经是一大
    //顶堆
    function adjustHeap(A, i, length) {
      let temp = A[i]; // 当前父节点
    // j<length 的目的是对结点 i 以下的结点全部做顺序调整
      for(let j = 2*i+1; j<length; j = 2*j+1) {
        temp = A[i];  // 将 A[i] 取出,整个过程相当于找到 A[i] 应处于的位置
        if(j+1 < length && A[j] < A[j+1]) { 
          j++;   // 找到两个孩子中较大的一个,再与父节点比较
        }
        if(temp < A[j]) {
          swap(A, i, j) // 如果父节点小于子节点:交换;否则跳出
          i = j;  // 交换后,temp 的下标变为 j
        } else {
          break;
        }
      }
    }
    
    // 堆排序
    function heapSort(A) {
      // 初始化大顶堆,从第一个非叶子结点开始
      for(let i = Math.floor(A.length/2-1); i>=0; i--) {
        adjustHeap(A, i, A.length);
      }
      // 排序,每一次for循环找出一个当前最大值,数组长度减一
      for(let i = Math.floor(A.length-1); i>0; i--) {
        swap(A, 0, i); // 根节点与最后一个节点交换
        adjustHeap(A, 0, i); // 从根节点开始调整,并且最后一个结点已经为当
                             // 前最大值,不需要再参与比较,所以第三个参数
                             // 为 i,即比较到最后一个结点前一个即可
      }
    }
    
    let Arr = [4, 6, 8, 5, 9, 1, 2, 5, 3, 2];
    heapSort(Arr);
    alert(Arr);

    程序注释: 将 i 结点以下的堆整理为大顶堆,注意这一步实现的基础实际上是:假设 结点 i 以下的子堆已经是一个大顶堆,adjustHeap 函数实现的功能是实际上是:找到 结点 i 在包括结点 i 的堆中的正确位置。后面做第一次堆化时,heapSort 中写了一个 for 循环,从第一个非叶子结点开始,对每一个非叶子结点都执行 adjustHeap 操作,所以就满足了每一次 adjustHeap 中,结点 i 以下的子堆已经是一大顶堆。

    复杂度分析:adjustHeap 函数中相当于堆的每一层只遍历一个结点,因为
    具有n个结点的完全二叉树的深度为[log2n]+1,所以 adjustHeap 的复杂度为 O(logn),而外层循环共有 f(n) 次,所以最终的复杂度为 O(nlogn)。

    堆的应用

    堆主要是用来实现优先队列,下面是优先队列的应用示例:

    而实现优先队列采用普通数组、顺序数组和堆的不同复杂度如下:

    3849855272-5b3d8da3ef28f_articlex[1].jpg

    使用堆来实现优先队列,可以使入队和出队的复杂度都很低。

    以上就是本文的全部内容,希望对大家的学习有所帮助,更多相关内容请关注PHP中文网!

    相关推荐:

    JS实现归并排序

    JS实现希尔排序

    以上就是JS实现堆排序的详细内容,更多请关注php中文网其它相关文章!

    声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn核实处理。
    专题推荐:javascript 排序 堆排序
    上一篇:原生JS基于window.scrollTo()封装垂直滚动动画工具函数 下一篇:自己动手写 PHP MVC 框架(40节精讲/巨细/新人进阶必看)

    相关文章推荐

    • 一文探究Angular中的服务端渲染(SSR)• react怎么实现列表排序• 深入浅析Node中的进程和线程• 一文聊聊Node包管理发展的五个阶段• 带你了解Angular组件间进行通信的几种方法
    1/1

    PHP中文网