建堆的效率分析

部分转载自https://blog.csdn.net/wangqing_199054/article/details/20461877https://www.cnblogs.com/shytong/p/5364470.html

现在常有两种建堆的方法,而这两种方法又有着不同的时间复杂度。下面分别陈述:

(1)自顶向下的建堆方式

这种建堆的方法具有O(n*log2n)的时间复杂度。从根结点开始,然后一个一个的把结点插入堆中。当把一个新的结点插入堆中时,需要对结点进行调整,以保证插入结点后的堆依然是大根堆。如下图所示,是采用自顶向下的方法建立的大根堆。

其中h = log2(n+1)-1,第k层结点个数为2k个(当然最后一层结点个数可能小于2h)。第k层的一个结点插入之后需要进行的比较(移动)次数为k。于是总的比较(移动)次数为∑k*2k(k = 0,1,2,...,h)。可以求得∑k*2k(k = 0,1,2,...,h)=(log2(n+1)-2)*(n+1)+2 = O(n*log2n)

(2)弗洛伊德建堆

    粗粗来看前面buildmaxheap的时间复杂度,每次maxHeapify调整需要的时间为lg(n), 总共要遍历的元素有N/2个,所以大致的运行时间复杂度为O(nlgn).

    如果我们更进一步分析,会发现它的实际情况会更理想一点。首先一个,我们从第a.length/2个元素开始执行maxHeapify,最开始这一层的元素只有一个子结点,也就是说,就算要调整,顶多一次就搞定了,不需要走lgn这么多步。

    要做进一步的分析,我们可以先思考一下我们要建的这个完全二叉树堆的几个特性。以如下图为例:

    我们看这棵二叉树,它必须保证每一层填满之后才能去填充下一层。而且,如果从根结点开始计数,往下第i层的元素如果不是最后一层的话,这一层的元素数量为2**i(2的i次方)。这样,对于一棵高为h的二叉树,它的所有结点数目就等于前面完全填满的层元素加上最下面一层的元素。

    为什么要把他们分开来计数呢?是因为最下面一层的元素有一个变动的范围,作为一棵高度为h的树,最下面一层的元素最少可以是1,最大可以是把整层填充满,也就是2**(h+1)。这样,他们求和的结果就是最少为2**h,最大为2**(h+1)。

所以假设堆的元素数量为n的话,我们就可以推出:

 

 结合这一步分析,我们可以得到: h <= lgn < h + 1。

个人觉得:这个不等式应该为 2^{h-1}\leq n< 2^{h}-1< 2^{h}。所以 h-1 <= lgn < h 。所以一般高度为logn取下整+1

结论1:

我们可以发现一个n个元素的树,它的高度相当于logn(向下取整)。

我们再来看我们分析的第二个结论。对应树每一个高度的一层,该有多少个元素呢?假设高度为1的那一层他们的元素个数为k,那么他们的访问时间复杂度为O(k)。根据前面的分析,我们还发现一个情况,就是如果从根结点开始计数,往下第i层的元素如果不是最后一层的话,这一层的元素数量为2**i(2的i-1次方)。正好这个第i层就相当于树的总高度减去当前层的结点的高度。假设第i层的高度为h,那么也就是i = lgn - h。

注:1.在一个高度为h的结点上运行max_heapify的时间为O(h)。

        2.含有n个结点的堆(或完全二叉树)的高度为logn

        3.每一层的结点数为:

所以堆的分析如下所述:

结论2:

这一层的元素数量为:

 那么他们所有元素的运算时间总和为如下:

 根据如下公式:(这个1/2是来源于公式)

 

则有:

 

 现在,我们发现,buildMaxHeap方法的时间复杂度实际上为O(n).

猜你喜欢

转载自blog.csdn.net/qq_31984717/article/details/84429926
今日推荐