heap中的heapify與依次壓入隊列的差異

標準的堆插入元素的算法很好理解,而且也很容易知道向堆中插入一個元素的代價是lgn。

按照最常規的想法,把一個數組中所有元素添加到一個堆中,依次壓入即可。壓入n個元素的代價就是

Sum[Log2[i],{1,i,n}]。

結果等於Log2[n!].根據斯特林公式,n!在n趨向於無窮大時可以近似看成(2Pi*n)^1/2*(n/E)^n。

因此,總代價可以看成n*Log2[n/E]+o(n),比O(n)的階要大一些。

直覺上,這個操作的複雜度應該是要比這個低的。因爲將n個元素完整排序的代價不過是nlgn。而堆應該是一個半排序。不應該接近nlgn。

這個直覺是正確的,heapify操作正是一個線性時間的算法.

舉個python的標準庫heapq裏面的實現

def cmp_lt(x, y):
    # Use __lt__ if available; otherwise, try __le__.
    # In Py3.x, only __lt__ will be called.
    return (x < y) if hasattr(x, '__lt__') else (not y <= x)
def heapify(x):
    """Transform list into a heap, in-place, in O(len(x)) time."""
    n = len(x)
    # Transform bottom-up.  The largest index there's any point to looking at
    # is the largest with a child index in-range, so must have 2*i + 1 < n,
    # or i < (n-1)/2.  If n is even = 2*j, this is (2*j-1)/2 = j-1/2 so
    # j-1 is the largest, which is n//2 - 1.  If n is odd = 2*j+1, this is
    # (2*j+1-1)/2 = j so j-1 is the largest, and that's again n//2-1.
    for i in reversed(xrange(n//2)):
        _siftup(x, i)
def _siftdown(heap, startpos, pos):
    newitem = heap[pos]
    # Follow the path to the root, moving parents down until finding a place
    # newitem fits.
    while pos > startpos:
        parentpos = (pos - 1) >> 1
        parent = heap[parentpos]
        if cmp_lt(newitem, parent):
            heap[pos] = parent
            pos = parentpos
            continue
        break
    heap[pos] = newitem
def _siftup(heap, pos):
    endpos = len(heap)
    startpos = pos
    newitem = heap[pos]
    # Bubble up the smaller child until hitting a leaf.
    childpos = 2*pos + 1    # leftmost child position
    while childpos < endpos:
        # Set childpos to index of smaller child.
        rightpos = childpos + 1
        if rightpos < endpos and not cmp_lt(heap[childpos], heap[rightpos]):
            childpos = rightpos
        # Move the smaller child up.
        heap[pos] = heap[childpos]
        pos = childpos
        childpos = 2*pos + 1
    # The leaf at pos is empty now.  Put newitem there, and bubble it up
    # to its final resting place (by sifting its parents down).
    heap[pos] = newitem
    _siftdown(heap, startpos, pos)


heapify操作主要有3個過程,heapify對一半的元素調用_siftup,而_siftup又調用了_siftdown。

這個過程作用的原理是什麼?

_siftup(heap,pos)最開始部分的代碼很明顯,就是把pos處的元素沿着最小路徑一路向下降到底。途中所比較的子節點依次上浮一層。而_siftdown(heap,startpos,ps)過程則是標準的堆插入動作,將pos處的元素上浮直到小於startpos時停止,也就是startpos的左兄弟節點或上一層節點。


這可以理解爲一個從倒數第二層開始構建堆的過程。第一輪循環把最下面兩層的3個節點的元素變成堆,再依次向上,通過插入一個上一層元素,將兩個堆合併。因爲每上一層,這層下面的兩個元素分別都是這兩棵子樹的最小元素。

這個過程可以看成類似數學歸納法的原理。第一個元素成立,而n成立確保n+1成立,因此對所有元素就成立。逐個插入的問題規模是線性增長的,比如比n小的元素是n-1,而歸併操作的問題規模是折半的。這就相當於在逐次插入元素的時候,當插到第四個元素的時候,停止向原堆插入,而向新堆插入。當兩個堆平衡以後,再將兩個堆合併。

因此,他的時間T(n)=2T(n/2)+2lg(n/2)

這剛好落入了主方法的第一種情況,因此他的複雜度是O(n)


舉個7個元素的例子。x=[a,b,c,d,e,f,g]。單純計算比較次數

在最壞情況下,逐個插入算法

1.插入a

2.插入b,a與b比較一次

3.插入c,a魚c比較一次

4.插入d,d與b比較一次,再與a比較一次

5.插入e,e與b比較一次,再與a比較一次

6.插入f,f與c比較一次,在與a比較一次

7.插入g,g與c比較一次,再與a比較一次


共計比較10次。


而heapify算法:

1.a與b、c各比較一次

2.d與e、f各比較一次

3.a與d比較一次

4.g與a比較一次

5.b與c比較一次

6.g與b比較一次


共計比較8次。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章