Python实现优先级队列结构的方法详解


Posted in Python onJune 02, 2016

最简单的实现
一个队列至少满足2个方法,put和get.
借助最小堆来实现.
这里按"值越大优先级越高"的顺序.

#coding=utf-8 
from heapq import heappush, heappop 
class PriorityQueue: 
  def __init__(self): 
    self._queue = [] 
 
  def put(self, item, priority): 
    heappush(self._queue, (-priority, item)) 
 
  def get(self): 
    return heappop(self._queue)[-1] 
 
q = PriorityQueue() 
q.put('world', 1) 
q.put('hello', 2) 
print q.get() 
print q.get()

 使用heapq模块来实现
下面的类利用 heapq 模块实现了一个简单的优先级队列:

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]

下面是它的使用方式:

>>> 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')
>>>

仔细观察可以发现,第一个 pop() 操作返回优先级最高的元素。 另外注意到如果两个有着相同优先级的元素( foo 和 grok ),pop操作按照它们被插入到队列的顺序返回的。

 函数 heapq.heappush() 和 heapq.heappop() 分别在队列 _queue 上插入和删除第一个元素, 并且队列_queue保证第一个元素拥有最小优先级(1.4节已经讨论过这个问题)。 heappop() 函数总是返回”最小的”的元素,这就是保证队列pop操作返回正确元素的关键。 另外,由于push和pop操作时间复杂度为O(log N),其中N是堆的大小,因此就算是N很大的时候它们运行速度也依旧很快。

在上面代码中,队列包含了一个 (-priority, index, item) 的元组。 优先级为负数的目的是使得元素按照优先级从高到低排序。 这个跟普通的按优先级从低到高排序的堆排序恰巧相反。

index 变量的作用是保证同等优先级元素的正确排序。 通过保存一个不断增加的 index 下标变量,可以确保元素按照它们插入的顺序排序。 而且, index 变量也在相同优先级元素比较的时候起到重要作用。

为了阐明这些,先假定Item实例是不支持排序的:

>>> a = Item('foo')
>>> b = Item('bar')
>>> a < b
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unorderable types: Item() < Item()
>>>

如果你使用元组 (priority, item) ,只要两个元素的优先级不同就能比较。 但是如果两个元素优先级一样的话,那么比较操作就会跟之前一样出错:

>>> a = (1, Item('foo'))
>>> b = (5, Item('bar'))
>>> a < b
True
>>> c = (1, Item('grok'))
>>> a < c
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unorderable types: Item() < Item()
>>>

通过引入另外的 index 变量组成三元组 (priority, index, item) ,就能很好的避免上面的错误, 因为不可能有两个元素有相同的 index 值。Python在做元组比较时候,如果前面的比较以及可以确定结果了, 后面的比较操作就不会发生了:

>>> a = (1, 0, Item('foo'))
>>> b = (5, 1, Item('bar'))
>>> c = (1, 2, Item('grok'))
>>> a < b
True
>>> a < c
True
>>>

如果你想在多个线程中使用同一个队列,那么你需要增加适当的锁和信号量机制。 可以查看12.3小节的例子演示是怎样做的。

深入思考
函数 heapq.heappush() 和 heapq.heappop() 分别在队列 _queue 上插入和删除第一个元素, 并且队列_queue保证第一个元素拥有最小优先级(1.4节已经讨论过这个问题)。 heappop() 函数总是返回”最小的”的元素,这就是保证队列pop操作返回正确元素的关键。 另外,由于push和pop操作时间复杂度为O(log N),其中N是堆的大小,因此就算是N很大的时候它们运行速度也依旧很快。

在上面代码中,队列包含了一个 (-priority, index, item) 的元组。 优先级为负数的目的是使得元素按照优先级从高到低排序。 这个跟普通的按优先级从低到高排序的堆排序恰巧相反。

index 变量的作用是保证同等优先级元素的正确排序。 通过保存一个不断增加的 index 下标变量,可以确保元素按照它们插入的顺序排序。 而且, index 变量也在相同优先级元素比较的时候起到重要作用。

为了阐明这些,先假定Item实例是不支持排序的:

>>> a = Item('foo')
>>> b = Item('bar')
>>> a < b
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unorderable types: Item() < Item()
>>>

如果你使用元组 (priority, item) ,只要两个元素的优先级不同就能比较。 但是如果两个元素优先级一样的话,那么比较操作就会跟之前一样出错:

>>> a = (1, Item('foo'))
>>> b = (5, Item('bar'))
>>> a < b
True
>>> c = (1, Item('grok'))
>>> a < c
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unorderable types: Item() < Item()
>>>

通过引入另外的 index 变量组成三元组 (priority, index, item) ,就能很好的避免上面的错误, 因为不可能有两个元素有相同的 index 值。Python在做元组比较时候,如果前面的比较以及可以确定结果了, 后面的比较操作就不会发生了:

>>> a = (1, 0, Item('foo'))
>>> b = (5, 1, Item('bar'))
>>> c = (1, 2, Item('grok'))
>>> a < b
True
>>> a < c
True
>>>

如果你想在多个线程中使用同一个队列,那么你需要增加适当的锁和信号量机制。 可以查看12.3小节的例子演示是怎样做的。

heapq 模块的官方文档有更详细的例子程序以及对于堆理论及其实现的详细说明。

Python 相关文章推荐
探索Python3.4中新引入的asyncio模块
Apr 08 Python
Python实现监控程序执行时间并将其写入日志的方法
Jun 30 Python
Python实现的中国剩余定理算法示例
Aug 05 Python
Python操作MySQL数据库的三种方法总结
Jan 30 Python
python程序 创建多线程过程详解
Sep 23 Python
opencv3/C++ 平面对象识别&amp;透视变换方式
Dec 11 Python
python3使用Pillow、tesseract-ocr与pytesseract模块的图片识别的方法
Feb 26 Python
tensorflow使用CNN分析mnist手写体数字数据集
Jun 17 Python
解决python 虚拟环境删除包无法加载的问题
Jul 13 Python
python批量合成bilibili的m4s缓存文件为MP4格式 ver2.5
Dec 01 Python
Pytorch1.5.1版本安装的方法步骤
Dec 31 Python
ubuntu安装jupyter并设置远程访问的实现
Mar 31 Python
KMP算法精解及其Python版的代码示例
Jun 01 #Python
Python缩进和冒号详解
Jun 01 #Python
Python注释详解
Jun 01 #Python
深入理解python try异常处理机制
Jun 01 #Python
python学习 流程控制语句详解
Jun 01 #Python
python+Django+apache的配置方法详解
Jun 01 #Python
python中函数默认值使用注意点详解
Jun 01 #Python
You might like
使用 MySQL 开始 PHP 会话
2006/12/21 PHP
PHP处理excel cvs表格的方法实例介绍
2013/05/13 PHP
php共享内存段示例分享
2014/01/20 PHP
写一段简单的PHP建立文件夹代码
2015/01/06 PHP
PHP提高编程效率的20个要点
2015/09/23 PHP
PHP简单判断字符串是否包含另一个字符串的方法
2016/03/25 PHP
PHP上传Excel文件导入数据到MySQL数据库示例
2016/10/25 PHP
jquery $.ajax入门应用二
2008/11/19 Javascript
Mootools 1.2教程(21)——类(二)
2009/09/15 Javascript
javascript 放大镜 v1.0 基于Yui2 实现的放大镜效果
2010/03/08 Javascript
js修改table中Td的值(定义td的单击事件)
2013/01/10 Javascript
iframe异步加载实现点击左边菜单加载右边内容实例讲解
2013/03/04 Javascript
javascript中节点的最近的相关节点访问方法
2013/03/20 Javascript
关于ExtJS4.1:快捷键支持的问题
2013/04/24 Javascript
通过复制Table生成word和excel的javascript代码
2014/01/20 Javascript
javascript 动态创建表格
2015/01/08 Javascript
JQuery删除DOM节点的方法
2015/06/11 Javascript
js实现表单多按钮提交action的处理方法
2015/10/24 Javascript
javascript实现图片轮播效果
2016/01/20 Javascript
bootstrap table小案例
2016/10/21 Javascript
jQuery实现合并表格单元格中相同行操作示例
2019/01/28 jQuery
node.js基于dgram数据报模块创建UDP服务器和客户端操作示例
2020/02/12 Javascript
微信小程序实现带放大效果的轮播图
2020/05/26 Javascript
react使用antd表单赋值,用于修改弹框的操作
2020/10/29 Javascript
用Python实现一个简单的多线程TCP服务器的教程
2015/05/05 Python
Python中的super用法详解
2015/05/28 Python
Python操作Redis之设置key的过期时间实例代码
2018/01/25 Python
Django项目中使用JWT的实现代码
2019/11/04 Python
python实现ftp文件传输系统(案例分析)
2020/03/20 Python
python实现邮件循环自动发件功能
2020/09/11 Python
美国流行背包品牌:JanSport(杰斯伯)
2018/03/02 全球购物
英文简历中的自我评价用语
2013/12/09 职场文书
2014收银员工作总结范文
2014/12/16 职场文书
护士2015年终工作总结
2015/04/29 职场文书
2017寒假社会实践心得体会范文
2016/01/14 职场文书
如何制定销售人员薪酬制度?
2019/07/09 职场文书