python模块之heapq模块的一些应用

2,146 阅读3分钟

堆是非线性的树形的数据结构,有2种堆,最大堆与最小堆。python的heapq模块默认的是最小堆。堆数据结构最重要的特征是heap[0] 永远是最小的元素。

最大堆:树中父节点的值总是大于等于任意子节点的值

最小堆:树中父节点的值总是大于等于任意子节点的值

我们一般使用二叉堆来实现优先级队列,它的内部调整算法复杂度为logN

1.堆排序的问题

1.1 利用heappush+heappop

import heapq
​
​
def sorted_func(iterable):
    new_list = []
    for element in iterable:
        heapq.heappush(new_list, element)
    return [heapq.heappop(new_list) for _ in range(len(new_list))]
​
​
if __name__ == '__main__':
    list1 = [5, 8, 3, 9, 0, -3, 12, 4, 8]
    print(sorted_func(list1))

result:

[-3, 0, 3, 4, 5, 8, 8, 9, 12]

1.2 利用nlargest或nsmallest

import heapq
​
list1 = [5, 8, 3, 9, 0, -3, 12, 4, 8]
print(heapq.nsmallest(len(list1), list1))

result:

[-3, 0, 3, 4, 5, 8, 8, 9, 12]

使用nlargest就相当于从大到小排序,和nlargest用法一致,这里就不再赘述

1.3 利用heapify

import heapq
​
list1 = [5, 8, 3, 9, 0, -3, 12, 4, 8]
heapq.heapify(list1)
print(list1)

result:

[-3, 0, 3, 4, 8, 5, 12, 9, 8]

2.优先级队列的问题

import heapq
​
​
class PriorityQueue(object):
​
    def __init__(self):
        self._queue = []
        self.index = 0
​
    def push(self, priority, item):
        heapq.heappush(self._queue, (-priority, self.index, item))
        self.index += 1
​
    def pop(self):
        return heapq.heappop(self._queue)[-1]
​
​
if __name__ == '__main__':
    q = PriorityQueue()
    q.push(2, 'two')
    q.push(1, 'one')
    q.push(-2, 'fei')
    q.push(12, '123')
    print(q.pop())
    print(q.pop())

result:

123
two

在上述代码中,队列包含了(-priority, index, item)的元组。优先级为负数是为了从高到低进行排序。设置index变量是为了保证同等优先级元素的正确排序,这里相同优先级时会按照插入顺序进行排序(当然,你也可以按照插入顺序倒序排列,只要-index就可以。更或者可以自定义排序方式来替代index)。而且,index 变量也在相同 优先级元素比较的时候起到重要作用。

2.2 阐述说明

为了对这一点做出说明,先来看个例子,假设item不支持排序。

class Item:
    def __init__(self, name):
        self.name = name
    def __repr__(self):
        return 'Item({!r})'.format(self.name)
a = Item('a')
b = Item('b')
print(a < b)

result:

Traceback (most recent call last):
  File "D:/interview/interview/22.py", line 11, in <module>
    print(a < b)
TypeError: '<' not supported between instances of 'Item' and 'Item'

2.2.1 (priority, item)二元元组的情形

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

a = (1, Item('a'))
b = (2, Item('b'))
print(a < b)

result:

True
c = (1, Item('first'))
print(a < c)

result:

Traceback (most recent call last):
  File "D:/interview/interview/22.py", line 13, in <module>
    print(a < c)
TypeError: '<' not supported between instances of 'Item' and 'Item'

2.2.2 (priority, index, item)三元元组的情形

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

a = (1, 0, Item('a'))
b = (2, 1, Item('b'))
print(a < b)

result:

True
c = (1, 2, Item('first'))
print(a < c)

result:

True

另外,如果你想在多个线程中使用同一个队列,那么你需要增加适当的锁和信号量机制。