结论:

  堆初始化的时间复杂度为 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}} \rightarrow N ,$

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

 

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

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

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

    3)深度为i的那一层结点总数为$2^{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}} \rightarrow N ,$

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

 

 

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

  

 

posted on 2020-11-23 23:32  新望  阅读(1424)  评论(0编辑  收藏  举报