用heapq模塊實現按優先級排序的優先級隊列

需求:

  1. 實現一個按優先級排序的隊列,能夠push和pop數據
  2. 在隊列上每次pop都是返回優先級最高的元素
  3. 如果優先級相同,按它們最初被加入時的順序返回
  4. 如果優先級發生改變,你該如何將其移至新的位置?

使用標準庫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,每天至少一篇技術文章,適合技術點滴積累,利用瑣碎時間學習技術的人。

發佈了113 篇原創文章 · 獲贊 42 · 訪問量 11萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章