堆的定義:n個元素的序列{k1,k2,ki,…,kn}當且僅當滿足下關係時,稱之爲堆。
(ki <= k2i,ki <= k2i+1)或者(ki >= k2i,ki >= k2i+1), (i = 1,2,3,4...n/2)
堆是一種重要的線性數據結構,通常被看作是一棵樹的數組對象。(堆總是一個完全二叉樹)
由於二叉樹良好的形態已經包含了父節點和孩子節點的關係信息,因此就可以不使用鏈表而簡單的使用數組來存儲堆。
Python沒有獨立的堆類型,而只有一個包含一些堆操作函數的模塊: heapq模塊
heapq模塊提供了堆隊列算法的實現,也稱爲優先隊列算法。
堆是完全二叉樹,且每個父節點的值都小於或等於它的任何子節點,heapq模塊的堆算法算法實現的數組heap中,對於所有的索引值(0~n-1)都滿足 heap[k] <= heap[2*k+1] 且 heap[k] <= heap[2*k+2] 。 爲了便於比較,不存在的元素被認爲是無限的。
這個模塊有關的API與教科書中描述的堆算法有兩個不同之處:
(a)使用基於零的索引。這使得節點的索引與其子節點的索引之間的關係稍微不那麼明顯,但是更適合,因爲Python使用基於零的索引。
(b)pop方法只返回最小的項;它最小的元素總是根元素heap[0](min heap)
1. 函數heappush用於在堆中添加一個元素。
注意,不能將它用於普通列表,而只能用於使用各種堆函數創建的列表。原因是元素的順序很重要
如果使用普通列表,不報錯但是結果不對的。
2. 函數heappop彈出最小的元素(總是位於索引0處),並確保剩餘元素中最小的那個位於索引0處(保持堆特徵)。雖然彈出列表中第一個元素的效率通常不是很高,但這不是問題,因爲heappop會在幕後做些巧妙的移位操作。
3.函數heapify通過執行儘可能少的移位操作將列表變成合法的堆(具備堆特徵)
4. 函數heapreplace 它從堆中彈出最小的元素,再壓入一個新元素。相比於依次執行函數heappop和heappush,這個函數的效率更高。
5. nlargest(n, iterable, key=None)和nsmallest(n, iterable, key=None):分別用於找出可迭代對象iterable中最大和最小的n個元素
注意: 如果需要重複使用這些函數,可以考慮將iterable轉換爲實際的堆。
查找最大或最小的 N 個元素?
當要查找的元素個數相對比較小的時候,函數 nlargest() 和 nsmallest() 是很合適的。
如果你僅僅想查找唯一的最小或最大(N=1)的元素的話,那麼使用 min() 和max() 函數會更快些。
如果 N 的大小和集合大小接近的時候,通常先排序這個集合然後再使用切片操作會更快點(sorted(items)[:N] 或者是 sorted(items)[-N:]
堆的應用:海量實數中(一億級別以上)找到TopK(一萬級別以下)的數集合。
-
A 排序:通常遇到找一個集合中的TopK問題,想到的便是排序,因爲常見的排序算法例如快排算是比較快了,然後再取出K個TopK數,時間複雜度爲O(nlogn),當n很大的時候這個時間複雜度還是很大的; 對於一億數據來說,A方案大約是26.575424*n;
-
B 堆隊列:由於我們只需要TopK,因此不需要對所有數據進行排序,可以利用堆的思想,維護一個大小爲K的小頂堆,然後依次遍歷每個元素e, 若元素e大於堆頂元素root,則刪除root,將e放在堆頂,然後調整爲小頂堆,時間複雜度爲logK;這樣遍歷一遍後,最小堆裏面保留的數就是我們要找的topK,整體時間複雜度爲O(k+n*logk)約等於O(n*logk),大約是13.287712*n(由於k與n數量級差太多),這樣時間複雜度下降了約一半。在python的nlargest(n, iterable, key=None)的實現中實現了這種算法:
if key is None:
it = iter(iterable)
# put the range(n) first so that zip() doesn't
# consume one too many elements from the iterator
result = [(elem, i) for i, elem in zip(range(n), it)]
if not result:
return result
_heapify_max(result)
top = result[0][0]
order = n
_heapreplace = _heapreplace_max
for elem in it:
if elem < top:
_heapreplace(result, (elem, order))
top = result[0][0]
order += 1
result.sort()
return [r[0] for r in result]
A、B、兩個方案中,當K和n的數量級相差越大,B方式越有效。