微信公众号:冰咖啡与狗

1. 什么是优先级队列?

优先级队列是一种容器型数据结构,它能管理一队记录,并按照排序字段(例如一个数字类型的权重值)为其排序。由于是排序的,所以在优先级队列中你可以快速获取到最大的和最小的值。

可以认为优先级队列是一种修改过的普通队列:普通队列依据记录插入的时间来获取下一个记录,而优先级队列依据优先级来获取下一个记录,优先级取决于排序字段的值。

优先级队列常用来解决调度问题,比如给紧急的任务更高的优先级。以操作系统的任务调度为例:高优先级的任务(比如实时游戏)应该先于低优先级的任务(比如后台下载软件更新)执行。

在 Python 中,内置的标准库提供了两种实现优先队列的数据结构,分别是 heapq 模块和 PriorityQueue 模块,接下来本文将对如何利用这两个数据结构实现优先级队列进行介绍,包括高优先级队列、低优先级队列,以及面向对象的优先级队列。这两种数据结构,其本质都是利用了堆的数据结构来实现优先队列,因此最后会利用列表来实现一个堆排序,然后在实现优先级队列。

先简单介绍 heap(堆)的概念。堆,是对于每一个父节点上的值都小于或等于子节点的值的二叉树。此外,一个堆必须是一个完整的二叉树,除了最底层,其他每一级必须是被完整填充的。因此,堆的最重要的一个特点就是:首项 heap[0] 总是最小的一项。

2. heapq 模块

heapq 是一个二叉堆的实现,其内部使用内置的 list 对象,对于列表中的每一个元素,其满足 a[k] <= a[2*k+1] and a[k] <= a[2*k+2] ,因此,该方法默认的是一个最小堆,a[0] 为队列中的最小元素。

2.1. 最小优先队列实现

由于 heapq 模块默认的就是最小堆结构,所以可以直接利用其中的函数操作来实现最小优先队列的功能。

def smallest_queue(arr):

"""默认为最小优先队列"""

print("原数组:{0}".format(arr))

# 将给定的列表转化为最小堆,线性时间

heapq.heapify(arr)

print("最小堆数组:{0}".format(arr))

# 插入元素

heapq.heappush(arr, 5)

print("插入新元素后:{0}".format(arr))

# 弹出最小元素

item0 = heapq.heappop(arr)

print("弹出的元素后:{0}".format(arr))

# 返回最小元素

item1 = arr[0]

print("获取最小元素的值:{0}".format(item1))

# 弹出最小元素,并插入一个新的元素,相当于先 heappop, 再 heappush

item2 = heapq.heapreplace(arr, -2)

print("弹出的元素为:{0}".format(item2))

print("现在的堆结构为:{0}".format(arr))

2.2. 最大优先队列的实现

除了最小堆以外,最大堆也是我们常用的一种数据结构,利用 heapq 模块可以实现最大堆的一些简单操作。

def largest_queue(arr):

"""最大优先队列的实现阅读 heapq 模块的源代码可以发现,其内置了最大优先队列的实现函数和操作函数,但没有内置新插入元素的函数"""

print("原数组:{0}".format(arr))

# 将给定的列表转化为最大堆,线性时间

heapq._heapify_max(arr)

print("最大堆数组:{0}".format(arr))

# 弹出最大元素

item0 = heapq._heappop_max(arr)

print("弹出的元素为:{0}".format(item0))

print("弹出的元素后:{0}".format(arr))

# 弹出最大元素,并插入一个新的元素

item1 = heapq._heapreplace_max(arr, 9)

print("弹出的元素为:{0}".format(item1))

print("现在的堆结构为:{0}".format(arr))

2.3. 复杂结构的优先队列

在现实场景中,我们在使用优先队列时往往不是针对简单的数组进行优先排列,而是对元组、列表或者字典等一个个复杂的对象进行优先排列。下面以元组为例,其在构建优先队列时,默认是以第一个元素来进行排列,当第一个元素相同时,则开始比较第二个。字符串的比较从首字母开始进行比较。

def object_queue():

"""针对对象结构的优先队列"""

q = []

heapq.heappush(q, (2, 'code'))

heapq.heappush(q, (1, 'eat'))

heapq.heappush(q, (3, 'sleep'))

heapq.heappush(q, (2, 'play'))

heapq.heappush(q, (3, "debug"))

q1 = [x for x in q]

while q:

next_item = heapq.heappop(q)

print(next_item)

# ---- result -----

# (1, 'eat')

# (2, 'code')

# (2, 'play')

# (3, 'debug')

# (3, 'sleep')

# 返回最小的 n 个元素,相当于 sorted(iterable, key=key)[:n]

n_smallest = heapq.nsmallest(3, q1, key=lambda x: x[0])

print("最小的3个元素:{0}".format(n_smallest))

# 返回最大的 n 个元素,相当于 sorted(iterable, key=key, reverse=True)[:n]

n_largest = heapq.nlargest(3, q1, key=lambda x: x[1])

print("最大的3个元素:{0}".format(n_largest))

3. PriorityQueue 模块

该模块定义的优先级队列,其内部使用了 heapq 模块,所以它的时间复杂度和heapq是相同的。

当一个对象的所有元素都是可比较的时,默认情况下是根据队列中的对象的第一个元素进行排序,越小的优先级越高,排在越前面。当第一个元素相同时,依次比较后续的元素的大小来进行排序。

由于 PriorityQueue 是继承自 Queue 类,所以很多函数的用法可以直接参照于 Queue 类中的函数。

from queue import PriorityQueue as PQ

pq = PQ()

pq.put((1, 'a'))

pq.put((2, 'c'))

pq.put((2, 'b'))

pq.put((2, 'b'))

print(pq.queue) # [(1, 'a'), (2, 'b'), (2, 'b'), (2, 'c')]

item0 = pq.get() # (1, 'a')

print(pq.queue) # [(2, 'b'), (2, 'b'), (2, 'c')]

print(pq.qsize()) # 优先队列的尺寸

while not pq.empty():

print(pq.get())与 heapq 模块不同的是,PriorityQueue 是基于类实现的,其提供的操作是同步的,提供锁操作,支持并发的生产者和消费者。

4. 实现自己的优先队列

在面向对象的编程过程中,我们通常是将一些单独的函数或变量组合成一个对象,然后在进行优先级排列。例如我们现在有很多种汽车,汽车有名字和价格,以及一些操作方法。当我们对汽车对象来按照价格进行优先级排列时,由于自定义的对象是不可比较的,所以在进行优先级排列时会报错。因此对于那些自定义的对象,我们需要重写优先级队列的方法来进行实现。

由于 PriorityQueue 也是基于 heapq 实现的,所以我们自定义的优先级队列可以直接基于 heapq 模块来实现。

import heapq

class My_PriorityQueue(object):

def __init__(self):

self._queue = []

self._index = 0

def push(self, item, priority):

"""队列由 (priority, index, item) 形式组成priority 增加 "-" 号是因为 heappush 默认是最小堆index 是为了当两个对象的优先级一致时,按照插入顺序排列"""

heapq.heappush(self._queue, (-priority, self._index, item))

self._index += 1

def pop(self):

"""弹出优先级最高的对象"""

return heapq.heappop(self._queue)[-1]

def qsize(self):

return len(self._queue)

def empty(self):

return True if not self._queue else False

class Car(object):

def __init__(self, name, value):

self.name = name

self.value = value

def __repr__(self):

return "{0}--{1}".format(self.name, self.value)

if __name__ == "__main__":

car1 = Car("BMW", 45)

car2 = Car("Maybach", 145)

car3 = Car("Bugatti", 85)

car4 = Car("Cadillac", 78)

car5 = Car("Maserati", 85)

pq = My_PriorityQueue()

pq.push(car1, car1.value)

pq.push(car2, car2.value)

pq.push(car3, car3.value)

pq.push(car4, car4.value)

pq.push(car5, car5.value)

print("队列大小:{0}".format(pq.qsize()))

# 弹出元素

while not pq.empty():

print(pq.pop())

5. 源码文件

python优先级排序_用Python实现优先级队列的3种方法相关推荐

  1. python dictionary排序_对Python的字典进行排序

    我们知道Python的内置dictionary数据类型是无序的,通过key来获取对应的value.可是有时我们需要对dictionary中 的item进行排序输出,可能根据key,也可能根据value ...

  2. python 时间序列预测_使用Python进行动手时间序列预测

    python 时间序列预测 Time series analysis is the endeavor of extracting meaningful summary and statistical ...

  3. python 概率分布模型_使用python的概率模型进行公司估值

    python 概率分布模型 Note from Towards Data Science's editors: While we allow independent authors to publis ...

  4. python numpy读取数据_大神教你python 读取文件并把矩阵转成numpy的两种方法

    导读 今天小编就为大家分享一篇python 读取文件并把矩阵转成numpy的两种方法,具有很好的参考价值,希望对大家有所帮助.一起跟随小编过来看看吧 在当前目录下: 方法1: file = open( ...

  5. python大神读取_大神教你python 读取文件并把矩阵转成numpy的两种方法

    导读 今天小编就为大家分享一篇python 读取文件并把矩阵转成numpy的两种方法,具有很好的参考价值,希望对大家有所帮助.一起跟随小编过来看看吧 在当前目录下: 方法1: file = open( ...

  6. Python进阶之使用Scrapy实现自动登录Github的两种方法(POST,FormRequest,from_response)

    Python进阶之使用Scrapy实现自动登录Github的两种方法 1. 通过.FormRequest()实现登录github github1.py 2. 通过.FormRequest.from_r ...

  7. python中用def实现自动排序_用 python 实现各种排序算法

    常见集中排序的算法 归并排序 归并排序也称合并排序,是分治法的典型应用.分治思想是将每个问题分解成个个小问题,将每个小问题解决,然后合并. 具体的归并排序就是,将一组无序数按n/2递归分解成只有一个元 ...

  8. python将对象放入列表根据某个属性排序_关于python:如何根据对象的属性对对象列表进行排序?...

    我有一个python对象列表,我想按对象本身的属性排序.列表如下: >>> ut [, , , , , , ...] 每个对象都有一个计数: >>> ut[1].c ...

  9. python汉字排序_【IT专家】Python中文排序(转载)

    本文由我司收集整编,推荐下载,如有疑问,请与我司联系 Python 中文排序(转载) 2012/02/02 3793 Python 比较字符串大小时,根据的是 ord 函数得到的编码 值.基于它的排序 ...

最新文章

  1. Android Java使用JavaMail API发送和接收邮件的代码示例
  2. 经济学与计算机科学结合,理论计算机在物理学和经济学领域的重要作用
  3. CSS3--幽灵按钮特效(实例)
  4. 停止Java线程,小心interrupt()方法
  5. 跟我一起学Redis之高可用从主从复制开始
  6. MongoDB Shell操作
  7. 京东壕掷27亿买下一座酒店 官方回应:以办公为主!
  8. 什么是平板电脑的杀手锏?
  9. mac vscode配置c++ debug环境
  10. 【蓝桥省赛倒计时】B组Java冲刺打卡(三)
  11. 安装Office InfoPath 2007
  12. Kotlin - 伴生对象与静态成员
  13. SpringBoot 优雅地对接口进行数据加解密
  14. vrchat新手教程_VRChat入门指南| 最新电脑资讯
  15. Hadoop MapReduce Job 相关参数设置 概念介绍与理解
  16. 2019年年终个人总结
  17. java 设计模式 路由器_Java设计模式——工厂模式
  18. win7出现无法连接到代理服务器的错误,不能上网的问题的解决
  19. vue-父组件向子组件共享数据
  20. 02.1 知识图谱工具 Protege的下载安装与使用

热门文章

  1. Java开发工具IntelliJ IDEA创建Andriod项目示例说明
  2. 排序二叉树的插入、删除操作(递归方式)。
  3. 深入学习.NET Web Services概念
  4. HDU2001 计算两点间的距离【入门】
  5. HDU2093 考试排名【排序】
  6. POJ1945 Power Hungry Cows【DFS】
  7. CCF NOI1061 Wifi密码
  8. 2016CCF-CCSP竞赛:第1题-虚拟机设计(共3题)
  9. HDU2022 海选女主角【入门】
  10. I00013 鸡兔同笼