JS排序算法总结:(七)堆排序

目的:掌握 堆排序 的 基本思想与过程、代码实现、时间复杂度

1、基本思想与过程:

  (1)将数组看做是一棵二叉树,并且是完全二叉树,将其变为最大二叉堆,即每一个父节点均大于其相连的子节点;

  (2)将堆顶元素与末尾元素交换,并重新调整为最大堆,这时,堆中末尾元素为最大元素,即数组中的最大元素已经乖乖在最后一个位置了;

  (3)重复 2 过程,直至所有元素都变得有序。

2、代码实现:

// 堆排序的主程序
function heapSort(arr){
    // 1、构建最大堆
    buildMaxHeap(arr)
    // 2、从堆尾出发,依次 交换堆尾与堆头的值;并将 除堆尾外的 剩余堆 重新调整为最大堆
    for(var i = arr.length-1 ; i>=0 ; i--){
        swap(arr,0,i)
        maxHeapify(arr,0,i) //此时 i 作为 heapSize 传入 maxHeapify,即 忽略了最后一个已经排好的元素
    }
    return arr
}

function swap(a,i,j){
    var temp = a[i]
    a[i] = a[j]
    a[j] = temp
}
function buildMaxHeap(arr){
    // 从 末尾节点的父节点 开始调整,直至根节点
    var iParent = Math.floor(arr.length/2)-1
    for(var i = iParent ; i>=0 ; i--){
        maxHeapify(arr,i,arr.length)
    }
}
function maxHeapify(arr,index,heapSize){ // heapSize 会一直变小,直至为0
    var iMax = index
    var iLeft = 2*index+1
    var iRight = 2*(index+1)
    if(iLeft < heapSize && arr[iLeft] > arr[iMax]){ 
        iMax = iLeft
    }
    if(iRight < heapSize && arr[iRight] > arr[iMax]){ // 注意 这里 是 iMax 而不是 index!!
        iMax = iRight
    }
    if(iMax != index){
        swap(arr,iMax,index)
        maxHeapify(arr,iMax,heapSize) // 注意 这里是iMax 而不是 index ! 因为新的 arr[index] 作为 父节点 已经在此轮完成堆调整了
    }
}

上面的代码实现里,用到了递归调用  maxHeapify() 函数,通常来说,递归主要用在分治法中,而这里并不需要分治。递归调用需要压栈/清栈,和迭代相比,性能上有略微的劣势。

接下来我们跑个题,聊一下,递归和迭代,递归和迭代都是循环中的一种,他们的区别是:

  (1)递归是重复调用函数自身实现循环,在使用递归时,必须有一个明确的递归结束条件,称为递归出口。一般分为以下两个阶段:

    a)递推:把复杂的问题的求解推到比原问题简单一些的问题的求解;

    b)回归:当获得最简单的情况后,逐步返回,依次得到复杂的解。

  (2)迭代是函数内某段代码实现循环,而迭代与普通循环的区别是:循环代码中参与运算的变量同时是保存结果的变量,当前保存的结果作为下一次循环计算的初始值。

  (3)空间利用率:

    递归是将一个问题分解为若干相对小一点的问题,遇到递归出口再原路返回,因此必须保存相关的中间值,这些中间值压入栈保存,问题规模较大时会占用大量内存

    迭代是逐渐逼近,用新值覆盖旧值,直到满足条件后结束,不保存中间值,空间利用率高

  (4)应用:

    递归可以解决很多问题:如背包问题,汉诺塔问题,斐波那契数列等;

    迭代经典例子就是实数的累加,比如计算1-100所有实数的和。

    一般情况下,递归中一定有迭代,但是迭代中不一定有递归,大部分可以相互转换。因此,能用迭代的不用递归,递归调用函数,计算有重复,浪费空间,并且递归太深容易造成堆栈的溢出。

所以呢,还是迭代好,那我们将递归改成迭代吧~

function maxHeapify(arr,index,heapSize){ 
    while(true){
        var iMax = index
        var iLeft = 2*index+1
        var iRight = 2*(index+1)
        if(iLeft < heapSize && arr[iLeft] > arr[iMax]){ 
            iMax = iLeft
        }
        if(iRight < heapSize && arr[iRight] > arr[iMax]){
            iMax = iRight
        }
        if(iMax != index){
            swap(arr,iMax,index)
            index = iMax
        }else{
            break;
        }
    }
}

3、时间复杂度:非常稳定,为O(n㏒n)复杂度,最好情况与最坏情况一样。

posted @ 2018-08-30 19:07  Cassie_茜  阅读(628)  评论(0编辑  收藏  举报