3.5 实现一个优先级队列
在实际应用中,为了更便利地操作队列,我们需要将队列按优先级排序,并在队列上的每次pop()操作后返回优先级最高的元素。
下面使用heapq模块实现一个简单的优先级队列,相关代码(priority_queue.py)如下:
# 优先级队列 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)[-1]
优先级队列的使用方式如下(priority_queue.py):
class Item: def __init__(self, name): self.name = name def __repr__(self): return 'Item({!r})'.format(self.name) if __name__ == "__main__": priority_queue = PriorityQueue() priority_queue.push(Item('queue_1'), 1) priority_queue.push(Item('queue_2'), 3) priority_queue.push(Item('queue_3'), 5) priority_queue.push(Item('queue_4'), 3) print(priority_queue.__dict__.get('_queue')[0]) print(f'pop item is:{priority_queue.pop()}') print(priority_queue.__dict__.get('_queue')[0]) print(f'pop item is:{priority_queue.pop()}') print(priority_queue.__dict__.get('_queue')[0]) print(f'pop item is:{priority_queue.pop()}') print(priority_queue.__dict__.get('_queue')[0]) print(f'pop item is:{priority_queue.pop()}') print(priority_queue.__dict__)
执行py文件,输出结果如下:
(-5, 2, Item('queue_3')) pop item is:Item('queue_3') (-3, 1, Item('queue_2')) pop item is:Item('queue_2') (-3, 3, Item('queue_4')) pop item is:Item('queue_4') (-1, 0, Item('queue_1')) pop item is:Item('queue_1') {'_queue': [], '_index': 4}
由执行结果可知,第一个pop()操作返回了优先级最高的元素。
注意 如果两个有着相同优先级的元素(queue_2和queue_4),pop()操作是按照它们被插入到队列的顺序返回的——先插入的元素先返回。
heapq.heappush()和heapq.heappop()函数分别用于在队列_queue上插入和删除第一个元素。队列_queue保证第一个元素拥有最高优先级。
heappop()函数总是返回最小的的元素,这是保证队列pop()操作返回正确元素的关键。另外,由于push()和pop()操作时间复杂度为O(logN),其中N是堆的大小,因此即使N很大,执行操作的运行速度也很快。
在上面示例代码中,队列包含一个(-priority,index,item)的元组。设置优先级为负数的目的是使元素按照优先级从高到低排序。这与普通的按优先级从低到高排序的堆排序恰巧相反。
index变量的作用是保证同等优先级元素的正确排序。通过保存一个不断增加的index下标变量,可确保元素按照它们插入的顺序排序。index变量在相同优先级元素比较的情况下起到重要作用。
下面通过示例进一步讲解优先级队列,相关代码(priority_queue_1.py)如下:
class Item: def __init__(self, name): self.name = name if __name__ == "__main__": queue_1 = Item('queue_1') queue_2 = Item('queue_2') print(queue_1 < queue_2)
执行py文件,输出结果如下:
Traceback (most recent call last): File "/Users/lyz/Desktop/python-workspace/advanced_programming/chapter1/priority_queue_1.py", line 8, in <module> print(queue_1 < queue_2) TypeError: '<' not supported between instances of 'Item' and 'Item'
如果你使用元组(priority,item),只要两个元素的优先级不同就能比较。但当两个元素优先级一样时,比较操作就会出错,这时我们可以在代码priority_queue_1.py中做修改:
queue_1 = (1, Item('queue_1')) queue_2 = (3, Item('queue_2')) queue_3 = (1, Item('queue_3')) print(queue_1 < queue_2) print(queue_1 < queue_3)
输出结果如下:
True Traceback (most recent call last): File "/Users/lyz/Desktop/python-workspace/advanced_programming/chapter1/priority_queue_1.py", line 14, in <module> print(queue_1 < queue_3) TypeError: '<' not supported between instances of 'Item' and 'Item'
由此可见,通过引入另外的index变量组成三元组(priority,index,item),能很好地避免上面示例中的错误,因为不可能有两个元素有相同的index值。
Python在做元组比较时,如果前面的比较已经可以确定结果,后面的比较操作就不会发生了。我们可以继续在priority_queue_1.py中添加如下修改:
queue_1 = (1, 0, Item('queue_1')) queue_2 = (3, 1, Item('queue_2')) queue_3 = (1, 2, Item('queue_3')) print(queue_1 < queue_2) print(queue_1 < queue_3)
执行py代码,输出结果如下:
True True
如果想在多个线程中使用同一个队列,那么需要增加适当的锁和信号量机制。
有想深入了解heapq模块的读者可以仔细研读heapq模块的官方文档。