數據結構與Python——堆(heap)

一、堆是什麼


堆是用數組實現的二叉樹,所以它沒有父指針或者子指針。堆根據“堆屬性”來排序,“堆屬性”決定了樹中節點的位置。
堆的一般用法:
.構建優先隊列
.支持堆排序
.快速查找出集合的最大值或者最小值

二、堆屬性


堆分爲最大堆(max heap)和最小堆(min-heap),兩者的差別在於節點的排序方式。

最大堆中,父節點的值比每個子節點的值都要大,最小堆中,父節點的值比每一個子節點的值都要小。這就是所謂的“堆屬性”(heap property),並且這個屬性對堆中每一個節點都成立。
舉個栗子:
堆屬性
這是一個最大堆,因爲每一個父節點的值比子節點的值都要更大。10 比 7 和 2 都打,7比5 和1 都大。
根據這一屬性,那麼最大堆總是將其中的最大值存放在樹的根節點。而對於最小堆,根節點中的元素總是樹中的最小值。堆屬性非常的有用,因爲堆常常被當做優先隊列使用,因爲可以快速的訪問到“最重要”的元素。

注意:堆的根節點中存放的是最大或者最小元素,但是其他節點的排序順序是未知的。
例如,在一個最大堆中,最大的那一個元素總是位於 index 0 的位置,但是最小的
元素則未必是最後一個元素。--唯一能夠保證的是最小的元素是一個葉節點,但是不確定是哪一個。

2.1 堆和普通樹的區別
堆不能取代二叉搜索樹,它們之間確有一些相似的地方,但是又有一些不同的地方,下面是一下主要的不同之處。
1.節點的順序。在一個二叉搜索樹(BST)裏面,左邊的子節點必須小於父節點,右邊的子節點必須大於子節點。但是在堆裏面並非如此,在最大堆裏面,所有子節點必須比父節點的值更小,最小堆裏面則相反。
2.內存的佔用,普通樹佔用的內存空間比他們實際存儲的數據更多,你必須爲節點對象以及左/右子節點指針分配額外的存儲空間,而堆只用一個普通的數組結構來存儲,並不會用到指針。
3.平衡,一個二叉樹必須要在平衡的情況下,其大部分操作的時間複雜度才能保持在O(logn), 你可以以任意的順序插入和刪除數據,或者使用AVL樹或者紅黑樹,但是在堆裏面,我們不用保證整個樹是有序的狀態。我們只需要堆屬性被實現即可,所以堆中平衡不是問題。因爲堆的結構,所以保證O(log n)的性能。
4.搜索。在二叉搜索樹裏面搜索會很快,但是在堆中會很慢,因爲堆的目的是把最大或者最小節點前置以及保證插入和刪除相對快 所以搜索對於堆而言優先級並不是最高的。

**

來自數組的樹

**

用數組來展示樹狀的結構可能看起來很奇怪,當時在時間和空間上都是很高效。
接下來我們來展示舉個栗子:

[10,7,2,5,1]

就這麼多,除了簡單的數組以外,我們並不需要更多的存儲空間。
那麼,在不被允許使用指針的情況下我們如何知道那個節點是父節點,哪個是子節點呢?問得好!節點在數組中的位置index和它的父節點和子節點之間存在一個映射關係。
如果i是節點的索引,那麼下面的公式就給出了父節點以及子節點在數組中的位置。

parent(i) = floor((i-1)/2)
left(i) = 2i+1
right = 2i + 2

注意右節點(right(i))只是左節點(left(i))的值+1,左右節點在數組的位置總是相鄰的。讓我們用上面的規則來推導出數組的索引以及左右節點的位置:
在這裏插入圖片描述
注意:根節點10 並沒有父節點,因爲-1 不是一個有效的數組索引,同樣,節點2,5和1並沒有子節點,因爲這些索引已經超出了數組的大小,所以在我們使用這些索引的時候,我們必須確保計算的索引值有效。

三、python裏面堆的實現


Python裏面沒有專門的堆類型,而只有包含一個堆類型操作的模塊heapq(q表示隊列),它包含6個函數,其中前4個與堆操作直接相關。必須通過列表這種結構來實現的。
heappush(heap,x) 把元素x壓到堆中
heappop(heap) 彈出堆中的第一個元素
heapify() 讓列表具有堆屬性
heapreplace(heap,x) 替換堆中的最小元素,並用x代替
nlargest(n,heap) 找出堆中最大的n個元素
nsmallest(n,heap) 找出堆中最小的n個元素

函數heappush用於在堆中添加一個元素。請注意,不能將它用於普通列表,而只能用於使用各種堆函數創建的列表。原因是元素的順序很重要(雖然元素的排列順序看起來有點隨意,並沒有嚴格地排序)。

import heapq
from random import shuffle
data = list(range(10))
shuffle(data)
heap = []
for n in data:
	heappush(heap,n)
print(heap)
heappush(heap,0.5)
print(heap)

輸出結果:

[0, 1, 2, 3, 5, 9, 6, 8, 4, 7]
[0, 0.5, 2, 3, 1, 9, 6, 8, 4, 7, 5]

函數heapify通過執行儘可能少的移位操作將列表變成合法的堆(即具備堆特徵)。如果你的堆並不是使用heappush創建的,應在使用heappush和heappop之前使用這個函數。
eg:

heap =  [5, 8, 0, 3, 6, 7, 9, 1, 4, 2] 
heapify(heap)
print(heap)

可以看到輸出結果中會自動把最小值放在左邊,來滿足堆的屬性(heap property):

[0, 1, 5, 3, 2, 7, 9, 8, 4, 6]

heapreplace() 使用的頻率不是那麼高,它通過彈出堆中的最小元素,然後把第二個參數作爲元素放入堆中,相較於heappush()的移位操作,heapreplace()的效率來的會更快一些。
nlargest(n,iter) 和 nsmallest(n,iter)用於找出iter中的最大(小)的n個元素,這種任務也可以通過列表裏面的先排序(sorted)再切片完成,但是使用堆的方式操作起來效率更高,也會更加節省內存空間。
代碼示例:

from heapq import * 
from random import shuffle

data = list(range(10))
shuffle(data)
heap = []
for n in data:
	heappush(heap,n)
print(heap)
print(nlargest(3,heap))
print(nsmallest(3,heap))

輸出結果:

[0, 1, 3, 4, 2, 8, 7, 5, 6, 9]
[9, 8, 7]
[0, 1, 2]

四、heap模塊的延展使用


總結:
綜合我們本篇的講解,相信大家對於heap結構有了一定的瞭解,它通過最大堆(max heap)和最小堆(min heap)兩種形式來完成一些數據的創建和操作工作。
1.最大堆裏面,根節點的數據最大,父節點的數據要比子節點都大
2.最小堆裏面,根節點的數據最小,父節點的數據要比子節點都小
3.它和二叉搜索樹(BST)的區別在於,父節點和子節點的數據大小關係
4.堆的優勢在於插入和刪除,二叉搜索樹的優勢在於搜索
5.python裏面沒有專門的堆類型,通過列表來存儲,heapq中的函數來實現

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