heapq 【堆積佇列】
簡介
What is heapq?
heapq
提供 heap queue 演算法 (也稱為 priority queue algorithm; 優先權佇列演算法) 的實作。Heaps 是 binary trees (二叉樹),其中每個 parent node (父節點) 的值都小於或等於其任何 children node (子節點) 的值。此屬性使 heaps 對於實現優先權 queues 非常有用,其中具有最高優先權的項目始終位於前面。
heapq
實作了一個 min-heap,其中最小的元素始終位於 root。此 module 提供了向 heap 中添加元素、popping 最小項以及查找最小項而不 popping 它的函數。
How to Use heapq?
heapq
提供了幾個函數:
heapify(iterable)
:將 iterable 轉換為 heap 資料結構。heappush(heap, item)
:在 heap 中新增一個 item,保持 heap 不變。heappop(heap)
:Pops 並返回 heap 中最小的 item。heappushpop(heap, item)
:Pushes item 進 heap 中,然後 pops 並返回最小的項目。heapreplace(heap, item)
:Pops 並返回最小的 item,然後 push 新的 item;比 heappop 和 heappush 更有效率。nlargest(n, iterable)
、nsmallest(n, iterable)
:傳回資料集中包含 n 個最大或最小元素的 list 。
Why Use heapq?
當您需要不斷存取 collection 中最小,最大或經過一些修改的項目時,使用 heapq
可能會很有優勢。出於此類目的,它比維護排序 list 更有效,尤其是當 list 頻繁更改時。常見 cases 包括:
實施 priority queues。
對任務進行優先排序的 scheduling algorithms (調度演算法)。
Dijkstra 和其他需要 priority queue 的 graph algorithms。
When to Use heapq?
在以下情況下應該使用 heapq
:
您需要重複快速存取最小或最大的項目。
資料集是動態的,您需要高效率的插入和刪除。
實作依賴 priority queues 的演算法。
Heaps 非常適合需要頻繁訪問最小或最大元素,以及資料集動態變化的應用程序,特別是當元素的順序很重要時 (例如在優先級佇列中)。
Lists 非常適合需要透過 index 頻繁存取元素,或元素大多在末尾新增/刪除的場景。
Conclusion
Python 中的 heapq
module 是有效管理 heaps 和 priority queues 的強大工具。其功能簡單且針對效能進行了最佳化,使其成為基於優先順序的專案管理,必不可少的演算法和應用程式的首選。
Binary Tree, Binary Heap
Binary trees (二元樹) 是一種樹狀資料結構,其中每個 node (節點) 最多有兩個 children,稱為 left child 和 right child。以下是一些要點:
Root Node: 樹的最頂層 node。
Leaf Nodes: 沒有 children 的 node。
Depth: 從 root 到 node 的路徑長度。
Height: 從 node 到 leaf 的最長路徑的長度。
Binary Heaps
在 heapq
library 的上下文中, binary tree 被組織為 binary heap。 binary heap 是一棵完全二元樹,這意味著樹的所有層都被完全填充,除了最後一層,它是從左到右填充的。
Binary heaps 有兩種類型:
Min-Heap: 每個 node 的值都大於或等於其 parent 的值,最小值元素在 root。
Max-Heap: 每個 node 的值小於或等於其 parent 的值,最大值元素在 root。
Python 的 heapq
module 實作了 min-heap。
Binary heaps 數學公式和性質:
Parent Node Index: 如果一個節點位於 index
i
,則其父節點位於索引(i-1)//2
。Left Child Index: 對於 index
i
的 node,其左子節點位於索引2*i + 1
。Right Child Index: 對於 index
i
的 node,其右子節點位於索引2*i + 2
。
Binary heap 中 parent node index, left child index 和 right child index 的公式,適用於 min-heaps 和 max-heaps。這些公式並不是特定於 heap 的類型,而是特定於二元堆的一般結構。這些關係源自於 binary heap,作為完全 binary tree 的屬性,其中每個 level 都是從左到右填充的。
這是 binary heap 的簡單圖示:
在此 diagram 中:
1
是 root node。2
和3
是1
的 children。4
和5
是2
的 children,6
是3
的 children。
heappush(), heappop(), heapreplace()
將 item
元素新增至現有的 heap
堆疊中。heappush
函數會自動調整新元素的位置以維持 heap 屬性。
刪除並返回 heap
的最小元素。
將 item
元素新增至現有的 heap
堆疊中,然後 pop 並返回 heap
中最小的項目。此函數比 heappush()
和 heappop()
的組合更有效。
以新的 item
取代 heap
中的最小元素。此函數比 heappop()
和 heappush()
的組合更有效,並且在需要維護 heap 的大小,時使用。
最小的元素總是在 root (heap[0]
)。若要按排序順序取得所有元素,請連續使用 heappop()
。
如果您需要 max heap (頂部的最大元素),您可以透過否定它們,來反轉優先權。
Python 的 heapq
模組僅提供 min heap 實作。對於 max heap 可以:
對值取反值是
對第一個元素是取反值
使用 tuples 自訂 comparator (比較器)。
Heaps 不會保持具有相同 keys 的元素的穩定性 (即,它們的原始 order 可能不會被保留)。
如果穩定性很重要,請考慮排序或使用其他方法。
範例 – Basic Usage
Example 1: 使用 heapreplace
維護固定大小的 heap (Priority Queue)。
執行結果:
Example 2: 將 heap 與自訂物件一起使用
執行結果:
在使用複雜的資料結構 (如物件或 tuples) 時,請確保它們是可比較的。 Python's heap operations 會比較元素,因此 heap 的元素應該支援比較。
範例 – 計算中位數
Example 1: 使用 heapreplace
和 heappop
來執行中位數 Stream
執行結果:
該程式碼實現了一種資料結構,可以有效地即時計算數字 stream 的中位數。它使用兩個 heaps: min_heap
, max_heap
add_num(num, min_heap, max_heap)
: 為資料結構新增一個新數字num
。
Push to min heap:首先,將
num
pushedmin_heap
。但是,為了確保堆平衡,會將min_heap
的最小的數字 (root) 彈出並推入max_heap
。這是透過heapq.heappushpop(min_heap, num)
完成的。保持平衡:Python 中的
max_heap
是透過取負值來實現的,因為Python的heapq
只支援最小 heaps。因此,我們在 pushing 和 poppingmax_heap
時取反值。平衡 Heaps:如果
max_heap
最終有更多元素,則max_heap
中的最小數字(由於求反,實際上是最大的數字)將移動到min_heap
。這可以確保兩個堆具有相同數量的元素,或min_heap
多一個元素。
get_median(min_heap, max_heap)
: 計算目前的中位數。
偶數元素:如果兩個 heaps 的元素數量相同,則中位數是兩個 heaps 的 roots 的平均值。對於
max_heap
,該值被取反。(負負得正)元素個數為奇數:如果元素個數為奇數,則中位數是
min_heap
的 roots,因為它比max_heap
多一個元素。
該演算法保持兩個屬性:
Size 屬性:兩個堆的大小最多相差1。
順序屬性:
min_heap
中的每個元素都大於或等於max_heap
中的每個元素。
中位數是兩個 root 的平均值 (如果總大小為偶數) 或min_heap
的 root (如果總大小為奇數)。這是對 heaps 的巧妙利用,以確保在 O(log n)
插入 operation 之後,始終可以在 O(1)
時間內計算出中位數。
Last updated