1. 程式人生 > >堆的基本概念及常用操作

堆的基本概念及常用操作

  • 完全二叉樹:若設二叉樹的深度為n,除第n層外,其它各層 (1n1) 的結點數都達到最大個數,第n層所有的結點都連續集中在最左邊,這就是完全二叉樹

  • 滿二叉樹:一個二叉樹,如果每一個層的結點數都達到最大值,則這個二叉樹就是滿二叉樹。也就是說,如果一個二叉樹的層數為n,且結點總數是2n1 ,則它就是滿二叉樹

  • 堆(heap)又被為優先佇列(priority queue)。儘管名為優先佇列,但堆並不是佇列。回憶一下,在佇列中,我們可以進行的限定操作是dequeue和enqueue。dequeue是按照進入佇列的先後順序來取出元素。而在堆中,我們不是按照元素進入佇列的先後順序取出元素的,而是按照元素的優先順序取出元素
  • 堆的一個經典的實現是完全二叉樹(complete binary tree)。這樣實現的堆成為二叉堆(binary heap)。由於其它幾種堆(二項式堆,斐波納契堆等)用的較少,一般將二叉堆就簡稱為堆。
二叉堆滿足二個特性:
1.父結點的鍵值總是大於或等於(小於或等於)任何一個子節點的鍵值
2.每個結點的左子樹和右子樹都是一個二叉堆(都是最大堆或最小堆

當父結點的鍵值總是大於或等於任何一個子節點的鍵值時為最大堆。當父結點的鍵值總是小於或等於任何一個子節點的鍵值時為最小堆

堆的基本操作

堆的主要操作是插入刪除最小(最大)元素(元素值本身為優先順序鍵值,小元素享有高優先順序)。在插入或者刪除操作之後,我們必須保持該實現應有的性質: 1. 完全二叉樹

2. 每個節點值都小於或等於它的子節點
以下的所有操作都以以最小堆為例,最大堆是同樣的道理。

1. 堆的儲存

一般都用陣列來表示堆,i結點的父結點下標就為(i1)/2。它的左右子結點下標分別為2i+12i+2。如第0個結點左右子結點下標分別為1和2。
此處輸入圖片的描述

2. 插入

在插入操作的時候,將新插入的節點放在完全二叉樹最後的位置,再和父節點比較。如果new節點比父節點小,那麼交換兩者。交換之後,繼續和新的父節點比較…… 直到new節點不比父節點小,或者new節點成為根節點。這樣得到的樹,就恢復了堆的性質。
此處輸入圖片的描述

# insertion of a hep
def MinHeapFixup(a, i): """Fix up inplace from node i""" temp = a[i] # j is the index of parent node j = (i - 1) / 2 while i != 0 and j >= 0: if a[j] < temp: break a[i] = a[j] i = j j = (i - 1) / 2 a[i] = temp def MinHeapAddNumber(a, num): """insert a number in heap""" n = len(a) a.append(num) MinHeapFixup(a, n) if __name__ == '__main__': a = [1,2,3,4,5,6] MinHeapAddNumber(a, 4) print(a) # 列印的結果是 [1, 2, 3, 4, 5, 6, 4]

這個在python中已有自帶的官方庫heapq實現

from heapq import *
x = [9,7,6,4,3,1]
# build heap
heapify(x)
print(x)
# insert
heappush(x,2)
print(x)

'''列印結果如下'''
# 原始堆
[1, 3, 6, 4, 7, 9]
# 插入2之後的結果
[1, 3, 2, 4, 7, 9, 6]

3. 刪除

刪除操作只能刪除根節點。根節點刪除後,我們會有兩個子樹,我們需要基於它們重構堆。 讓最後一個節點last成為新的節點,從而構成一個新的二叉樹。再將last節點不斷的和子節點比較。如果last節點比兩個子節點中小的那一個大,則和該子節點交換。直到last節點不大於任一子節點都小,或者last節點成為葉節點。
此處輸入圖片的描述

# deletion of a heap
def MinHeapFixdown(a, i):
    """fix down in place from node i"""
    n = len(a)
    temp = a[i]
    # j is the left child node
    j = 2*i + 1
    while j < n:
        if j+1 < n and a[j+1] < a[j]:
            j += 1
        if a[j] > temp:
            break
        a[i] = a[j]
        i = j
        j = 2*i + 1
    a[i] = temp

def MinheapDeleteNumber(a):
    """delete a number in heap"""
    n = len(a)
    a[0], a[n-1] = a[n-1], a[0]
    a.pop()
    MinHeapFixdown(a, 0)

if __name__ == '__main__':
    b = [1,2,6,7,4,13,8]
    MinheapDeleteNumber(b)
    print(b)

# 列印的結果是
[2, 4, 6, 7, 8, 13]

用heapq實現是

x = [1, 3, 2, 4, 7, 9, 6]
# delete
min_value = heappop(x)
print(min_value)
print(x)

'''列印的結果是'''
1                   # 彈出的最小元素
[2, 3, 6, 4, 7, 9]  # 刪除後的結果

4. 堆化陣列(建堆)

有了堆的插入和刪除後,再考慮下如何對一個數據進行堆化操作。

# build MinHeap
def MakeMinHeap(a):
    n = len(a)
    for i in range(n/2-1, -1, -1):
        MinHeapFixdown(a, i)
if __name__ == '__main__':
    c = [9,5,3,1,6,7,3,10,8]
    MakeMinHeap(c)
    print(c)

# 列印的結果是
[1, 5, 3, 8, 6, 7, 3, 10, 9]

heapq直接使用heapify建堆

from heapq import *
x = [9,7,6,4,3,1]
# build heap
heapify(x)

'''列印的結果'''
[1, 3, 6, 4, 7, 9]  # 建好的堆

建堆的時間複雜度:O(n)

堆排序

首先可以看到堆建好之後堆中第0個數據是堆中最小的資料。取出這個資料再執行下堆的刪除操作。這樣堆中第0個數據又是堆中最小的資料,重複上述步驟直至堆中只有一個數據時就直接取出這個資料。

# heap sort
def HeapSort(nums):
    ans = []
    # 建堆
    MakeMinHeap(nums)
    while len(nums) > 1:
        ans.append(nums[0])
        MinheapDeleteNumber(nums)
    ans.append(nums[0])
    return ans

if __name__ == '__main__':
    nums = [1,4,2,5,6,8,4]
    print(HeapSort(nums))

# 列印的結果是
[1, 2, 4, 4, 5, 6, 8]
nums = [1, 3, 5, 7, 9, 2, 4, 6, 8, 0]
heapify(nums)
res = []
while nums:
    res.append(heappop(nums))
print res
'''列印的結果是'''
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

複雜度分析:

堆排序的時間複雜度:由於每次重新恢復堆的時間複雜度為O(logn),共n1次重新恢復堆操作,再加上前面建立堆時間複雜度O(n)。二次操作時間相加還是O(nlogn)