Python:优先级队列确实在相同的优先级元素

时间:2017-12-25 14:45:16

标签: python priority-queue

我正在使用python' Queue.PriorityQueue,并遇到了以下问题:当向队列中插入具有相同优先级的多个元素时,我会指望队列按插入顺序(FIFO)提供服务。出于某种原因,情况并非如此:

>>> from Queue import PriorityQueue
>>>
>>> j1 = (1, 'job1')
>>> j2 = (1, 'job2')
>>> j3 = (1, 'job3')
>>> j4 = (1, 'job4')
>>> 
>>> q = PriorityQueue()
>>> q.put(j1)
>>> q.put(j2)
>>> q.put(j3)
>>> q.put(j4)
>>> q.queue
[(1, 'job1'), (1, 'job2'), (1, 'job3'), (1, 'job4')]
>>> q.get()
(1, 'job1')
>>> q.queue
[(1, 'job2'), (1, 'job4'), (1, 'job3')]

从示例中可以看出,订单在一个get()之后混合。 原因是什么?如何克服(保持相同的prio元素的顺序)?

修改

有人要求我添加一个示例,表明q.get()实际上已经弄乱了FIFO排序,所以这里有一个精心设计的例子:

class Job(object):
    def __init__(self, type_, **data):
        self.type_ = type_
        self.priority = 0 if self.type_ == 'QUIT' else 1
        self.data = data

    def __cmp__(self, other):
        return cmp(self.priority, other.priority)

    def __repr__(self):
        return 'Job("' + self.type_ + '", data=' + repr(self.data) + ')' 

q = PriorityQueue()
q.put(Job('Build'))
q.put(Job('Clean'))
q.put(Job('QUIT'))
q.put(Job('Create'))
q.put(Job('Build'))
q.put(Job('Clean'))

现在我将逐个列出这些元素。预期的结果:QUIT首先退出,然后其余的,FIFO命令:构建,清理,创建,构建,清理:

>>> q.get()
Job("QUIT", data={})
>>> q.get()
Job("Build", data={})
>>> q.get()
Job("Clean", data={})
>>> q.get()
Job("Build", data={}) # <<---
>>> q.get()
Job("Clean", data={})
>>> q.get()
Job("Create", data={})

3 个答案:

答案 0 :(得分:3)

优先级队列"are often implemented with heaps"和Python也不例外。正如文档所说,它是"using the heapq module"。堆不自然地提供稳定性。这也是heapsort "is not a stable sort"的原因。如果你想要稳定,你需要自己强制执行。幸运的是,它就像存储条目"as 3-element list including the priority, an entry count, and the task"一样简单。

请注意,您为优先级和任务提供了Python的优先级队列,但队列并不关心。它没有将这两个值视为优先级和任务。它只是认为该对是一个“项目”,它甚至从未调查过它。只有我们用户将该对视为优先级和任务。所以你也可以单独给它任务字符串,没有额外的优先级。队列甚至都没有注意到。它并不试图提取一些优先权。对于它的优先级,它只询问整个项目是否小于另一个项目。这就是为什么当你想要优先处理任务而不仅仅是它们的自然顺序(例如,字符串'job1'小于字符串'job2')时,你使用优先级和任务元组。元组按字典顺序排序,如果(a, b)小于(c, d),或者它们相等且a小于{{c,则b小于d 1}}。因此,当队列询问这样一个元组是否小于另一个元组时,它是元组,它会查看自身并首先考虑优先级,然后可能是第二个任务。

此外,使用q.queue,您正在检查队列的基础数据结构。你不应该在意这一点。不知道为什么它甚至可以访问。但是如果你检查它,你需要将它看作堆,而不是把它当作排序列表。不是那个“订单混合了”就像你说的那样,就是你误解了那个列表。无论如何......您应该关注的顺序是您实际获得的顺序。使用q.get()。如果您只使用q.get()获取该示例的所有四个项目,您会看到 在您的广告订单中将它们提供给您。虽然那是因为你按排序顺序插入并且它们只有一个可能的顺序,因为没有相同的项目。你会先得到(1, 'job1') 而不是,因为它是先插入的,但因为它是四个元组中最小的(因为优先级是相同的,'job1'是最小的四个字符串)。并且你会得到(1, 'job2')而不是,因为它是第二个插入的,但因为它是第二小的项目。等等。如果您按照任何其他顺序插入它们,您仍然可以按顺序(1, 'job1')(1, 'job2')(1, 'job3')(1, 'job4')获取它们。

关于您添加的示例:您的Job个对象仅根据其优先级进行比较。而那些构建,清理,创建,构建和清理对象都具有相同的优先级。因此,就队列而言,它们都是平等的!这不像你的第一个例子,你的四个元组只允许一个可能的顺序。所以我们回到我刚才所说的话,堆不自然地提供稳定性,如果你想要稳定性,你应该添加一个条目数。看看我在那里链接的解释和配方。它使用列表作为堆并使用heapq函数,但您可以轻松地将其调整为使用PriorityQueue。虽然可以更好地定义您自己的StablePriorityQueue ,而不是那些单独的顶级辅助函数,作为PriorityQueue的子类或包装。

答案 1 :(得分:2)

正如here所述,Python PriorityQueue是用binary heap实现的。

二进制堆是一个二叉树,其中每个节点的值等于或大于其子节点的值。因此,在二进制堆中,根始终包含最小值。删除最小节点后,将重新组织堆,以便基本堆属性仍然有效。

堆通常使用数组实现,其中a[k]a[2*k]a[2*k+1]的父级。在Python中,q.queue就是这个数组。从堆中删除元素后,数组将以不保留原始顺序的方式重新排序。

答案 2 :(得分:1)

其他2个答案解释了会发生什么。

虽然我想为您提供另一种表达方式,以帮助您更好地理解。

我从此documentation page获取了关于heapq的快照。首先,您可以看到PriorityQueue使用了heappop here

现在,到图像。

heappop

在此图片中,当您弹出第一个项0job1)时,1(&#39; job2&#39;)将取代它,然后{ {1}}(3)会占用job41)。我们应该说这是一种正常的行为。