菜单 学习猿地 - LMONKEY

VIP

开通学习猿地VIP

尊享10项VIP特权 持续新增

知识通关挑战

打卡带练!告别无效练习

接私单赚外块

VIP优先接,累计金额超百万

学习猿地私房课免费学

大厂实战课仅对VIP开放

你的一对一导师

每月可免费咨询大牛30次

领取更多软件工程师实用特权

入驻
361
0

堆排序

原创
05/13 14:22
阅读数 46813
二叉堆
 
二叉堆是完全二叉树或者近似完全二叉树。
 
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;
}

 

发表评论

0/200
361 点赞
0 评论
收藏