借助 1)中的修改位置p处元素的权值操作:decrese(p,Δ)。将p处元素的权值降为负无穷大。此时,该元素会向上调整至堆顶,然后执行deleteMin即可。
三,建堆(buildHeap)
从最后一个非叶子结点开始向前进行向下调整。
1 /** 2 * Establish heap order property from an arbitrary 3 * arrangement of items. Runs in linear time. 4 */ 5 private void buildHeap( ) 6 { 7 for( int i = currentSize / 2; i > 0; i-- ) 8 percolateDown( i ); 9 }
i 的初始值为最后一个非叶子结点的位置。
时间复杂度分析:
建堆的时间复杂度与堆中所有的结点的高度相同。
分析如下:首先,叶子结点的高度为0。而建堆,就是从最后一个非叶子结点开始,不断调用percolateDown(i),而percolateDown(i)方法的时间复杂度就是位置 i 处节点的高度。在上面第7行for循环中,当 i 自减为1时,表明已经到了堆顶元素,因此整个buildHeap的时间复杂度就是所有非叶子结点的高度之和。而叶子结点的高度为0,故buildHeap的时间复杂度可理解成 整个二叉堆的所有的结点的高度之和。
而对于理想二叉堆而言:(二叉堆是一颗完全二叉树,理想二叉堆为满二叉树)
所有结点的高度之为:2^(h+1)-1-(h+1)。其中,h表示二叉堆的高度
又可以表示成:N-b(N),N是堆中结点的个数,b(N)是N的二进制表示法中1的个数,如:b(7)=3
四,d 堆
上面分析了二叉堆的基本操作。那什么是 d 堆呢?为什么要有 d 堆呢?
对于二叉堆,d=2。顾名思义,d堆就是所有节点都有d个儿子的堆。为什么需要这种堆?
分析二叉堆的基本操作,insert操作需要定位父结点,这需要一个除法操作,操作的次数与树的高度有关。deleteMin操作需要找出所有儿子中权值最小的那个儿子,而寻找儿子节点则需要乘法操作,操作的复杂度与儿子的个数有关(d越大,节点的儿子数越多,查找越慢)。
假设,我们的需求是有大量的insert操作,而仅有少量的deleteMin,那d堆从理论上讲就有性能优势了。因为d 远大于2时,树的高度很小啊,但是当d不是2的倍数时,除法操作不能通过移位来实现,也许会有一定的性能损失,这也是为什么insert操作分析中讲的“插入性能会有一定的提高”。
而如果有大量的deleteMin操作,那d堆反而可能会除低性能,因为:d 越大,说明节点的儿子个数越多,找出权值最小的儿子就需要更多的比较次数了。
可见,d堆的提出,是因为需求不同而导致的。比如,insert属于高频需求.....
五,二叉堆的不足
根据上面的分析,二叉堆的insert复杂度O(logN),deleteMin最坏也是O(logN)。
但是如果需要查找堆中某个元素呢?或者需要合并两个堆呢?