『Python CoolBook:heapq』數據結構和算法_heapq堆隊列算法&容器排序

1、heapq堆隊列算法模塊

本模塊實現了堆隊列算法,也叫做優先級隊列算法。堆隊列是一棵二叉樹,而且擁有這樣特色,它的父節點的值小於等於任何它的子節點的值。前端

本模塊實際上實現了一系列操做容器的方法,使之表現的如堆通常。python

一、基本使用

heapq.heappush(heap, item)

把一項值壓入list(用於表示堆heap),同時維持堆的排序要求,其特性是直接比較入列元素大小(包括入列元素爲容器的狀況),將大的放在後面。算法

import heapq

queue = []
heapq.heappush(queue, 5)
heapq.heappush(queue, 2)
heapq.heappush(queue, 1)
heapq.heappush(queue, 2)
queue
[1, 2, 2, 5] 

heapq.heappop(heap)

彈出並返回堆裏最小值的項,調整堆排序。若是堆爲空,拋出異常IndexErrorapi

heapq.heappop(queue)

 1app

heapq.heapify(x) 

就地轉換一個列表爲堆排序,時間爲線性。函數

nums = [1, 8, 2, 23, 7, -4, 18, 23, 42, 37, 2]
heapq.heapify(nums)
nums

[-4, 2, 1, 23, 7, 2, 18, 23, 42, 37, 8] spa

初始化後僅僅保證前三個元素最小的順序(最前端的樹分支),在後面headpop會依次維護隊列的輸出最小:code

for i in range(11):
    print(heapq.heappop(nums))

 -4 1 2 2 7 8 18 23 23 37 42orm

nums = [1, 8, 2, 23, 7, -4, 18, 23, 42, 37, 2]

for i in range(11):
    print(heapq.heappop(nums))

 1 2 -4 2 8 7 18 23 23 37 42blog

可見不初始化直接heapq.heappop前三個元素依然是最小的三個,可是不會被排序。

二、堆排序應用

問題

怎樣從一個集合中得到最大或者最小的N個元素列表?

解決方案

heapq模塊有兩個函數:nlargest()nsmallest() 能夠完美解決這個問題。

import heapq
nums = [1, 8, 2, 23, 7, -4, 18, 23, 42, 37, 2]
print(heapq.nlargest(3, nums)) # Prints [42, 37, 23]
print(heapq.nsmallest(3, nums)) # Prints [-4, 1, 2]

 和sorted相似,它們也接受關鍵字參數,

portfolio = [
    {'name': 'IBM', 'shares': 100, 'price': 91.1},
    {'name': 'AAPL', 'shares': 50, 'price': 543.22},
    {'name': 'FB', 'shares': 200, 'price': 21.09},
    {'name': 'HPQ', 'shares': 35, 'price': 31.75},
    {'name': 'YHOO', 'shares': 45, 'price': 16.35},
    {'name': 'ACME', 'shares': 75, 'price': 115.65}
]
cheap = heapq.nsmallest(3, portfolio, key=lambda s: s['price'])
expensive = heapq.nlargest(3, portfolio, key=lambda s: s['price'])

 總結

  • 當要查找的元素個數相對比較小的時候,函數nlargest() 和 nsmallest()是很合適的。
  • 若是你僅僅想查找惟一的最小或最大(N=1)的元素的話,那麼使用min()和max()函數會更快些。
  • 相似的,若是N的大小和集合大小接近的時候,一般先排序這個集合而後再使用切片操做會更快點(sorted(items)[:N] 或者是 sorted(items)[-N:])。
  • 須要在正確場合使用函數nlargest() 和 nsmallest()才能發揮它們的優點(若是N快接近集合大小了,那麼使用排序操做會更好些)。
  • 因爲push和pop操做時間複雜度爲O(N),其中N是堆的大小,所以就算是N很大的時候它們運行速度也依舊很快。

2、容器排序

python中兩個容器比較大小,會從第0個元素開始比較,相等則下一位比較,不相等則返回,也就是說即便後面元素數目不一致或者不能比較大小也可以比較容器出大小

class Item:
    def __init__(self, name):
        self.name = name
    def __repr__(self):
        return 'Item({!r})'.format(self.name)

a = Item('foo')
b = Item('bar')
a < b

# ---------------------------------------------------------------------------
# TypeError                                 Traceback (most recent call last)
# <ipython-input-34-3bf0061fd9c0> in <module>()
#       1 a = Item('foo')
#       2 b = Item('bar')
# ----> 3 a < b
# 
# TypeError: '<' not supported between instances of 'Item' and 'Item'

a = (1, Item('foo'))
b = (5, Item('bar'))
a < b

# True

 甚至,

(1,2,3,4)>(0,1)

# True

 3、優先級隊列實踐

import heapq

class PriorityQueue:
    def __init__(self):
        self._queue = []
        self._index = 0

    def push(self, item, priority):
        heapq.heappush(self._queue, (-priority, self._index, item))
        self._index += 1

    def pop(self):
        return heapq.heappop(self._queue)

class Item:
    def __init__(self, name):
        self.name = name
    def __repr__(self):
        return 'Item({!r})'.format(self.name)
>>> q = PriorityQueue() >>> q.push(Item('foo'), 1) >>> q.push(Item('bar'), 5) >>> q.push(Item('spam'), 4) >>> q.push(Item('grok'), 1) >>> q.pop() Item('bar') >>> q.pop() Item('spam') >>> q.pop() Item('foo') >>> q.pop() Item('grok') >>>

在上面代碼中,隊列包含了一個 (-priority, index, item) 的元組。優先級爲負數的目的是使得元素按照優先級從高到低排序。這個跟普通的按優先級從低到高排序的堆排序恰巧相反。

index變量的做用是保證同等優先級元素的正確排序。經過保存一個不斷增長的index下標變量,能夠確保元素安裝它們插入的順序排序。並且,index變量也在相同優先級元素比較的時候起到重要做用。

相關文章
相關標籤/搜索