《Python Cookbook(第3版)中文版》——1.5 实现优先级队列

1.5 实现优先级队列

1.5.1 问题

我们想要实现一个队列,它能够以给定的优先级来对元素排序,且每次pop操作时都会返回优先级最高的那个元素。

1.5.2 解决方案

下面的类利用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)返回的顺序同它们插入到队列时的顺序相同。

1.5.3 讨论

上面的代码片段的核心在于heapq模块的使用。函数heapq.heappush()以及heapq.heappop()分别实现将元素从列表_queue中插入和移除,且保证列表中第一个元素的优先级最低(如1.4节所述)。heappop()方法总是返回“最小”的元素,因此这就是让队列能弹出正确元素的关键。此外,由于push和pop操作的复杂度都是O(logN),其中N代表堆中元素的数量,因此就算N的值很大,这些操作的效率也非常高。

在这段代码中,队列以元组(-priority, index, item)的形式组成。把priority取负值是为了让队列能够按元素的优先级从高到低的顺序排列。这和正常的堆排列顺序相反,一般情况下堆是按从小到大的顺序排序的。

变量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()
>>>

通过引入额外的索引值,以(prioroty, 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模块的文档中有着详细的示例和相关讨论。

时间: 2024-10-29 23:07:39

《Python Cookbook(第3版)中文版》——1.5 实现优先级队列的相关文章

《Python Cookbook(第3版)中文版》——导读

前 言 自2008年以来,我们已经目睹了整个Python世界正缓慢向着Python 3进化的事实.众所周知,完全接纳Python 3要花很长的时间.事实上,就在写作本书时(2013年),大多数Python程序员仍然坚持在生产环境中使用Python 2.关于Python 3不能向后兼容的事实也已经做了许多努力来补救.的确,向后兼容性对于任何已经存在的代码库来说是个问题.但是,如果你着眼于未来,你会发现Python 3带来的好处绝非那么简单. 正因为Python 3是着眼于未来的,本书在之前的版本上

谁有&amp;amp;lt;&amp;amp;lt;CLR Via C#&amp;amp;gt;&amp;amp;gt;第三版中文版的电子书

问题描述 谁有<<CLRViaC#>>第三版中文版的电子书,我是个初学者,看网上推荐此书的人多,想看一下,我的QQ:330784617.谢谢!! 解决方案 解决方案二:试一试我一般看英文的,虽然很少看书:(解决方案三: 解决方案四:第二版有的,想看第三版.

拒绝从入门到放弃_《Python 核心编程 (第二版)》必读目录

目录 目录 关于这本书 必看知识点 最后 关于这本书 <Python 核心编程 (第二版)>是一本 Python 编程的入门书,分为 Python 核心(其实并不核心,应该叫基础) 和 高级主题 两大部分,以 Python 2.x 作为主要演示版本,涵盖的知识面广,知识点较齐全,代码多且好理解,但对 Python 版本特性的内容太久远,不合时宜. 整体来说 Python 核心 部分是主要内容,高级主题 部分作为应用扩展内容.后半部分篇幅较短,内容不够深入,只到了解的层面,好在横向够广(每一个主

(六十二)第四章总结——《C++ Primer Plus 第6版 中文版》

书是<C++ Primer Plus  第6版  中文版> 数组.指针.结构 是C++的3种复合类型.   注:为了方便,类型名用int为主,变量名用a为主.   数组: 包括数组(例如int a[10];)和字符串(例如char a[10];),还有string类(例如string a="abc";),vector类(例如vector<int>a(5)).array类(array<int,3>a)等. 数组名表示数组所在的(第一个元素)内存地址.

求大神解答一下-C++ primer plus 第6版 中文版 第16章复习题的一个问题

问题描述 C++ primer plus 第6版 中文版 第16章复习题的一个问题 奇葩的是课后居然没答案...... 求正规.严谨.简洁的标准答案! 程序清单16.15(在p708页):functor.cpp //functor.cpp--using a functor #include尖括号iostream尖括号 #include尖括号list尖括号 #include尖括号iterator尖括号 #include尖括号algorithm尖括号 template//functor class

【转】c++.primer.plus.第五版.中文版[下载]

c++.primer.plus.第五版.中文版[下载] 一共有5部分.全部下载完才可解压阅读. c++.primer.plus.第五版.中文版(一) c++.primer.plus.第五版.中文版(二) c++.primer.plus.第五版.中文版(三) c++.primer.plus.第五版.中文版(四) c++.primer.plus.第五版.中文版(五) "在遇到无法解决的问题时,我总会求助于C++ Primer一书."--Bruce Eckel,"编程思想"

《Python Cookbook(第2版)中文版》——1.15 扩展和压缩制表符

1.15 扩展和压缩制表符 任务 将字符串中的制表符转化成一定数目的空格,或者反其道而行之. 解决方案 将制表符转换为一定数目的空格是一种很常见的需求,用Python的字符串提供的expandtabs方法可以轻松解决问题.由于字符串不能被改变,这个方法返回的是一个新的字符串对象,是原字符串的一个修改过的拷贝.不过,仍可以将修改过的拷贝绑定到原字符串的名字: mystring = mystring.expandtabs( ) 这样并不会改变mystring原先指向的字符串对象,只不过将名字myst

《Python Cookbook(第2版)中文版》——1.13 访问子字符串

1.13 访问子字符串 任务 获取字符串的某个部分.比如,你读取了一条定长的记录,但只想获取这条记录中的某些字段的数据. 解决方案 切片是个好方法,但是它一次只能取得一个字段: afield = theline[3:8] 如果还需考虑字段的长度,struct.unpack可能更适合.比如: import struct # 得到一个5字节的字符串,跳过3字节,得到两个8字节字符串,以及其余部分: baseformat = "5s 3x 8s 8s" # theline超出的长度也由这个b

《Python Cookbook(第2版)中文版》——1.11 检查一个字符串是文本还是二进制

1.11 检查一个字符串是文本还是二进制 任务 在Python中,普通字符串既可以容纳文本,也可以容纳任意的字节,现在需要探知(当然,完全是启发式的试探:对于这个问题并没有什么精准的算法)一个字符串中的数据究竟是文本还是二进制. 解决方案 我们采取Perl的判定方法,如果字符串中包含了空值或者其中有超过30%的字符的高位被置1(意味着该字符的码值大于126)或是奇怪的控制码,我们就认为这段数据是二进制数据.我们得自己编写代码,其优点是对于特殊的程序需求,我们随时可以调整这种启发式的探知方式: f