zoukankan      html  css  js  c++  java
  • 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)复杂度,最好情况与最坏情况一样。

  • 相关阅读:
    C#中的多态
    一个JQUERY文件
    等比例缩放图片
    WIN7 环境下 VS2012 打开某些解决方案项目 提示 【已停止工作】 解决办法
    统计字符串中字符出现的次数
    LINQ关联表查询语法和.NET扩展方法和JSON.NET时间格式化代码段
    EasyUI表格datagrid合并行单元格
    一个导出Excel的类
    一个缩略图的类
    分布式缓存MemCache
  • 原文地址:https://www.cnblogs.com/CassieHouse/p/9562137.html
Copyright © 2011-2022 走看看