• 堆排序


    二叉堆
     
    二叉堆是完全二叉树或者近似完全二叉树。
     
    1.大顶堆: 所有节点的子节点都比自身小的堆
    2.小顶堆: 所有节点的子节点都比自身大的堆
     
    一般用数组来表示堆,假设节点I是数组A中下标为i的节点
    Parent(i)下标: (i-1)/2
    Left(i)下标: 2*i + 1
    Right(i)下标: 2*i + 2
    含有N个元素的堆A的高度是: Floor(log<2>N)
     
    堆排序
     
    堆排序利用了大顶堆(或小顶堆)堆顶记录的关键字最大(或最小)这一特性,
    使得在当前无序区中选取最大(或最小)关键字变得简单
     
    在堆排序算法中,用得是大顶堆,小顶堆通常在构造优先级队列时使用
     
    实际应用中,排序往往采用快排,而不是堆排,堆排主要用在优先级队列和top-K等问题上
     
    其基本思想为(大顶堆):
     
    1)将初始待排序关键字序列(R1,R2....Rn)构建成大顶堆,此堆为初始的无序区;
    2)将堆顶元素R[1]与最后一个元素R[n]交换,R[n]为数组中的最大值,此时得到新的无序区(R1,R2,......Rn-1)和新的有序区(Rn),有序区将收容最大值,次大值,依次进行,且满足R[1,2...n-1]<=R[n];
    3)由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,......Rn-1)调整为新堆,然后再次将R[1]与无序区最后一个元素交换,得到新的无序区(R1,R2....Rn-2)和新的有序区(Rn-1,Rn)。不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成。

    基本过程

    1.将无序数组转换为一个大顶堆
    2.将大顶堆的顶部元素与数组的最后一个元素交换
    3.将交换后的堆调整为一个大顶堆
    4.将堆顶与数组倒数第二个元素交换
    5.重复以上过程,直至堆中只有一个元素

    从上述过程可知,堆排序其实也是一种选择排序,是一种树形选择排序。只不过直接选择排序中,为了从R[1...n]中选择最大记录,需比较n-1次,然后从R[1...n-2]中选择最大记录需比较n-2次。事实上这n-2次比较中有很多已经在前面的n-1次比较中已经做过,而树形选择排序恰好利用树形的特点保存了部分前面的比较结果,因此可以减少比较次数。对于n个关键字序列,最坏情况下每个节点需比较log2(n)次,因此其最坏情况下时间复杂度为nlogn。堆排序为不稳定排序,不适合记录较少的排序。

    javascript实现

    function fHeapSort(arr) {
        //构建初始堆,将无序数组转化为堆
        var len = arr.length;
        //数组元素的顺序对应堆的从上到下,从左到右,因此前Math.floor(len / 2)个节点为非叶子节点,即堆顶,只需对所有堆顶完成调整即可
        //调整从后往前,即从最右下的堆顶开始调整,往左往上调整堆顶
        for (var i = Math.floor(len / 2); i--;) {
            fAdjustHeap(arr, i, len);
        }
        for (var i = len;i--;) {
            //将堆顶与数组的第i个元素交换
            var temp = arr[0];
            arr[0] = arr[i];
            arr[i] = temp;
            //交换后,重新构造大顶堆,构造大顶堆的起点一直的堆顶,但是长度却一直减小,只对无序区进行排序
            fAdjustHeap(arr, 0, i);
        }
        return arr;
    }
    
    function fAdjustHeap(arr, nParent, len) {
        //从父节点,左孩子,右孩子三个节点中选取最大者,与父节点交换,
        //对于子节点非孩子节点,与父节点交换后,其自身的大顶堆可能被破坏,
        //需要向下继续调整,直至到达堆边界,即叶子节点
        var temp = arr[nParent];
        //数组中的父节点的索引乘以2加1为其左孩子节点
        //如果发生交换,则其孩子堆也需要调整一次,找到其孩子堆的左孩子,步长为2*nChild+1,循环进行调整
        var nChild = 2 * nParent + 1;
        while (nChild < len) {
            //nChild + 1为右孩子节点,若右孩子大,则选择它
            if (nChild + 1 < len && arr[nChild] < arr[nChild + 1]) {
                nChild++;
            }
            //若父节点大,则无需交换,其孩子堆也无需调整,跳出循环
            if (temp >= arr[nChild]) {
                break;
            }
            //将大孩子赋值给父节点,由于上步是使用temp进行比较大小,相当于将temp与子节点进行交换,所以此处只需要将子赋值为父,不必将父与子交换
            arr[nParent] = arr[nChild];
            //parent跟着步长走,向下游走去,并逐级用大孩子覆盖父节点,直到父节点为大顶节点
            nParent = nChild;
            nChild = 2 * nParent + 1
        }
        //将最后的大顶节点与开始的调整节点交换
        arr[nParent] = temp;
    }
  • 相关阅读:
    编译 安装 infobright
    MySQL忘记密码恢复密码的实现方法
    Intel 服务器 架构 NUMA
    Centos 卸载 java
    vs2010 无法将文件“obj**”复制到“bin**”
    linux安装eclipse PyDev
    infobright 编译安装
    [转贴]==开手排车的八个绝招==
    [摘]广义企业级PDM系统下的PPM(工艺规划管理)
    中国皇帝定下佛教戒律:僧人不准吃肉
  • 原文地址:https://www.cnblogs.com/mengff/p/6160874.html
Copyright © 2020-2023  润新知