python-數據結構-堆-heapq模塊

堆的定義: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、兩個方案中,當Kn的數量級相差越大,B方式越有效。


https://www.itcodemonkey.com/article/2082.html《堆和堆的應用》

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