zoukankan      html  css  js  c++  java
  • 为什么要堆初始化-堆初始化时间复杂度?

    结论:

      堆初始化的时间复杂度为 O(N)

      插入成堆的时间复杂度为 O(N Log N)

     

    !!!阅读前需先了解完全二叉树,堆排序算法,不清楚移步

      完全二叉树

      堆排序

    堆排序伪代码:

    HEAPSORT( A )

      BUILD_MAX_HEAP(A);        //堆初始化,本文讨论的主题

      for i=A.length down to 2       

        exchange A[1] with A[i]

        A.length=A.length-1

        MAX_HEAPIFY(A,1)       //自上而下维护堆

    嘿!堆为什么要初始化?

      一日,在实现堆排序算法时,室友好奇的一问:你这个数组为什么要先初始化成堆呀?这么复杂,声明一个空堆,不断的把数组里的元素插入进去不就好了吗?

    欸!好像是这么一回事呢。那堆排序算法里的这个初始化是不是太多余了?

      首先,先说明维护堆的两个操作,作为接下来分析的基础:

    typedef int* HEAP;         //int为元素的堆

        1.自上而下的维护一棵子树为大根堆,在这里将该方法命名为:adjust_down(HEAP heap,int pos,int len)算法执行次数取决于该节点到叶子节点的距离。

    //算法演示
    void adjust_down(HEAP heap,int pos,int len)
    {
        heap[0]=heap[pos];
        for(int i=pos<<1;i<=len;i<<=1){
            if(i<len&&heap[i+1]>heap[i])
                i++;
            if(heap[i]>heap[0])
                heap[pos]=heap[i],pos=i;
            else break;
        }
        heap[pos]=heap[0];
    }

        2.自下而上的将一个元素插入到堆中合适的位置,在这里将该方法命名为:adjust_up(HEAP heap,int pos);算法执行次数取决于该节点到的距离。

    //算法演示
    void adjust_up(HEAP heap,int pos)
    {
        heap[0]=heap[pos];
        int parent=pos/2;
        while(parent>0&&heap[parent]<heap[0]){
            heap[pos]=heap[parent];
            pos=parent,parent/=2;
        }
        heap[pos]=heap[0];    
    }

    室友说的对吗?

      从描述上来看,首先空堆是满足条件的,在每次插入前堆都是大根堆,那么插入以后执行adjust_up(),最后确实生成了一棵大根堆。

      而堆的初始化过程为:从元素的第len/2个元素开始到第一个元素,不断的调用dajust_down(),最后也生成一个大根堆。

      两个方法最后都生成了大根堆。

     

    那为什么还要有堆初始化的过程呢?

      既然能实现同样的功能,那效率上是否有差异呢?

      最直观的,从空间上看,因为堆的实现方式是数组,如果先申请一个堆,在不断的往里面插入,那么堆的空间与数组空间一样大,需要相当于两个数组的容量。

    如果采用在数组上初始化,则不需要多余的空间。

      从时间上来看,堆初始化是自上而下,插入成堆是自下而上;

      堆初始化:因为每个节点需要的比较的次数取决该节点到叶子节点的距离(原因参考代码:adjust_down() )。

        1)设树的深度从0开始计数,树的深度为K,,结点个数为N。

        2)深度为K-i的结点,需要的比较次数为 i。

        3)除最后一层节点可能不满以外,深度为K-i的那一层结点总数为$2^{K-i}$

      故总的比较次数 $S=sum_{i=1}^{K} {2^{K-i}*i}$

      则$2*S=sum_{i=1}^{K} {2^{K-i+1}*i}$

      得$S=2*S-S=sum_{i=1}^{K} {2^{K}}+K  = 2^{K+1}-2+K$

      因为$sum_{i=1}^{K} {2^{K}} ightarrow N ,$

      故初始化的时间复杂度为 O(N),

      插入成堆:因为每个结点需要的比较次数取决于该结点到根的距离(原因参考代码:adjust_up() )。

        1)分析时,以一颗满二叉树为代表,以简化分析过程,其他相关参数如上定义。

        2)深度为i的结点,需要的比较次数为 i。

        3)深度为i的那一层结点总数为$2^{K-i}$

      故总的比较次数 $S=sum_{i=1}^{K} {2^{i}*i}$

      则$2*S=sum_{i=1}^{K} {2^{i+1}*i}$

      得$S=2*S-S=(K-1)*2^{K+1}+2^K-2$

      因为$sum_{i=1}^{K} {2^{K}} ightarrow N ,$

      故插入成堆的时间复杂度为O(N Log N)。

    综上所述,堆初始化在空间上,时间上均更有优势,所以是有必要的。

      

  • 相关阅读:
    layout布局
    窗口、easyui-window、easyui-panel、easyui-linkbutton
    FASTJSON
    Insert title here
    Insert title here
    Scala并发编程
    scala中java并发编程
    scala调用外部命令
    scala正则表达式
    scala占位符_的用法
  • 原文地址:https://www.cnblogs.com/xinwang-coding/p/14025081.html
Copyright © 2011-2022 走看看