需求:
- 實現一個按優先級排序的隊列,能夠push和pop數據
- 在隊列上每次pop都是返回優先級最高的元素
- 如果優先級相同,按它們最初被加入時的順序返回
- 如果優先級發生改變,你該如何將其移至新的位置?
使用標準庫heapq來一步步的實現。
heapq模塊提供了堆排序算法的實現。注意這裏指的是堆排序算法的實現而不是一個數據結構。
有關heapq模塊的詳細介紹可以查看官方文檔https://docs.python.org/zh-cn/3.6/library/heapq.html
需求1:
首先定義一個元素。
class Item:
def __init__(self, name):
self.name = name
def __repr__(self):
return 'Item({})'.format(self.name)
實現一個按優先級排序的隊列,具有push和pop方法。
class PriorityQueue:
def __init__(self):
self._queue = []
def push(self, item, priority):
heapq.heappush(self._queue, (priority, item))
def pop(self):
value = heapq.heappop(self._queue)
return value[-1]
pq = PriorityQueue()
pq.push(Item('dog'), 2)
pq.push(Item('cat'), 3)
pq.push(Item('person'), 1)
print(pq._queue)
print(pq.pop())
print(pq.pop())
print(pq.pop())
運行結果:
[(1, Item(person)), (3, Item(cat)), (2, Item(dog))]
Item(person)
Item(dog)
Item(cat)
通過pop的結果可以知道,heapq默認彈出優先級最小的元素
需求2:
在PriorityQueue裏將priority改爲相反數,從而達到每次pop都是返回優先級最高的元素。
class PriorityQueue:
def __init__(self):
self._queue = []
def push(self, item, priority):
# 通過改變priority來實現每次彈出最大元素
heapq.heappush(self._queue, (-priority, item))
def pop(self):
value = heapq.heappop(self._queue)
return value[-1]
執行結果:
[(-3, Item(cat)), (-2, Item(dog)), (-1, Item(person))]
Item(cat)
Item(dog)
Item(person)
需求3:
class PriorityQueue:
def __init__(self):
self._queue = []
self._index = 0
def push(self, item, priority):
# 通過改變priority來實現每次彈出最大元素
heapq.heappush(self._queue, (-priority, self._index, item))
self._index += 1
def pop(self):
value = heapq.heappop(self._queue)
return value[-1]
index
變量的作用是保證同等優先級元素的正確排序。 通過保存一個不斷增加的 index
下標變量,可以確保元素按照它們插入的順序排序,從而達到相同優先級元素先進先出的目的。
執行結果:
[(-3, 1, Item(cat)), (-2, 0, Item(dog)), (-2, 2, Item(person))]
Item(cat)
Item(dog)
Item(person)
需求4:
優先級發生改變,該如何將其移至隊列中的新位置,或者刪除
class PriorityQueue:
def __init__(self):
self._queue = []
self._index = 0
self.entry_finder = {}
self.removed_tag = 'removed'
def push(self, item, priority):
if item in self.entry_finder:
self.remove(item)
# 通過改變priority來實現每次彈出最大元素
entry = (-priority, self._index, item)
self.entry_finder[item] = entry
heapq.heappush(self._queue, entry)
self._index += 1
def pop(self):
while self._queue:
priority, count, task = heapq.heappop(self._queue)
if task is not self.removed_tag:
del self.entry_finder[task]
return task
raise KeyError('pop from an empty priority queue')
def remove(self, item):
entry = self.entry_finder.pop(item)
entry[-1] = self.removed_tag
pq = PriorityQueue()
pq.push(Item('dog'), 2)
pq.push(Item('cat'), 3)
pq.push(Item('person'), 2)
pq.push(Item('person'), 4)
print(pq._queue)
print(pq.pop())
print(pq.pop())
print(pq.pop())
運行結果:
[(-4, 3, Item(person)), (-3, 1, Item(cat)), (-2, 2, Item(person)), (-2, 0, Item(dog))]
Item(person)
Item(cat)
Item(dog)
可以使用字典來保存隊列中的元素,移除隊列中的元素或改變其優先級的操作實現起來更爲困難,因爲它會破壞堆結構的不變性。 因此,一種可能的解決方案是將元素標記爲已移除,再添加一個改變了優先級的新元素
關注公衆號:日常bug,每天至少一篇技術文章,適合技術點滴積累,利用瑣碎時間學習技術的人。