heapq制作优先级队列
admin
2024-02-29 21:45:40
0次

heapq制作优先级队列

1.概述

python里的队列都是先进先出队列,例如list创建的队列。他们会按照接收元素的顺序保存这些元素。
但有时候,我们想根据元素的重要程度来排序,在这种情况下,应该使用优先级队列。
使用堆可以实现优先级队列,那么列表实现的队列和堆实现的列表有什么区别那?

  • 列表实现的队列默认存储元素的顺序是添加元素的顺序,并且列表底层使用数组结构存储数据效率不高。
  • 堆实现队列不是按照添加元素的顺序存储,而是按照指定的属性排序存储,它底层使用二叉树存储数据,效率非常高。

2.使用普通队列存在的问题

2.1.开发一个图书管理功能

下面我们通过实现一个图书馆借书功能演示使用普通队列的问题。
假如我们要给图书馆写个程序,管理图书的出借和归还的工作。有的人能及时还书,有的人不按时还书,所以我们要提醒他们。下面是结束和还书的代码。

''' 创建图书对象'''
# Example 1
class Book:def __init__(self, title, due_date):''':param title: 书名:param due_date: 归还日期'''self.title = titleself.due_date = due_date# Example 2
def add_book(queue, book):queue.append(book)# 根据归还日期排序,最早归还日期在列表尾部queue.sort(key=lambda x: x.due_date, reverse=True)# 添加借书和归还日期
queue = []
add_book(queue, Book('Don Quixote', '2019-06-07'))
add_book(queue, Book('Frankenstein', '2019-06-05'))
add_book(queue, Book('Les Misérables', '2019-06-08'))
add_book(queue, Book('War and Peace', '2019-06-03'))# Example 3
class NoOverdueBooks(Exception):pass''' 返回逾期没有还书的图书'''
def next_overdue_book(queue, now):if queue:book = queue[-1]# 与当前日期比较,按照归还日期从远到近的顺序返回没有归还的书if book.due_date < now:queue.pop()return book# 如果没有预期没有归还的则返回消息raise NoOverdueBooks# Example 4
now = '2019-06-10'# 多次调用输出预期未换的书名
found = next_overdue_book(queue, now)
print(found.title)found = next_overdue_book(queue, now)
print(found.title)

运行上面的代码,根据逾期时间的远近关系返回了没有归还的图书

War and Peace
Frankenstein

2.2.测试图书借书和归还图书性能

1.测试借书性能

借书的过程是不断向列表中添加数据,因此我们来测试下向列表添加数据性能花费的时间就是借书消耗时间。

from timeit import timeit, repeat
import random
import timeitdef print_results(count, tests):avg_iteration = sum(tests) / len(tests)print(f'Count {count:>5,} takes {avg_iteration:.6f}s')return count, avg_iterationdef print_delta(before, after):before_count, before_time = beforeafter_count, after_time = aftergrowth = 1 + (after_count - before_count) / before_countslowdown = 1 + (after_time - before_time) / before_timeprint(f'{growth:>4.1f}x data size, {slowdown:>4.1f}x time')def list_overdue_benchmark(count):def prepare():to_add = list(range(count))random.shuffle(to_add)return [], to_adddef run(queue, to_add):for i in to_add:# 添加图书,并将列表中的顺序反转queue.append(i)queue.sort(reverse=True)tests = repeat(# prepare函数返回list和countsetup='queue, to_add = prepare()',stmt=f'run(queue, to_add)',globals=locals(),repeat=100,number=1)return print_results(count, tests)# Example 8
baseline = list_overdue_benchmark(500)
for count in (1_000, 1_500, 2_000):print()comparison = list_overdue_benchmark(count)print_delta(baseline, comparison)

运行上面的代码,从花费的时间可以看出它和队列长度呈线性增长。

Count   500 takes 0.001161sCount 1,000 takes 0.003619s2.0x data size,  3.1x timeCount 1,500 takes 0.007473s3.0x data size,  6.4x timeCount 2,000 takes 0.012954s4.0x data size, 11.2x time

2.测试还书性能

还书是从列表中删除数据过程,首先要在列表中找到待待换的图书,然后在列表中删除。这时,位于这条记录之后的数据都要向前移动一个位置,这样算下来,执行还书的时间也呈线性增长。

def list_return_benchmark(count):def prepare():queue = list(range(count))random.shuffle(queue)to_return = list(range(count))random.shuffle(to_return)return queue, to_returndef run(queue, to_return):for i in to_return:queue.remove(i)tests = repeat(setup='queue, to_return = prepare()',stmt=f'run(queue, to_return)',globals=locals(),repeat=100,number=1)return print_results(count, tests)# Example 10
baseline = list_return_benchmark(500)
for count in (1_000, 1_500, 2_000):print()comparison = list_return_benchmark(count)print_delta(baseline, comparison)

运行上面的代码,查看还书消耗的时间呈线性增长

Count   500 takes 0.000919sCount 1,000 takes 0.003649s2.0x data size,  4.0x timeCount 1,500 takes 0.008248s3.0x data size,  9.0x timeCount 2,000 takes 0.014748s4.0x data size, 16.0x time

3.使用优先级队列

使用list做队列保存很少的图书还可以,但是要保存的图书很多时候就会变得运行非常的慢,好在python内置了heapq模块可以解决这个问题,因为它能够高效的实现优先队列。
模块里heap指的是堆,他是一种数据结构,可以维护列表中的元素,并且只需要对数据级别的时间就可以添加或删除其中最小的元素。这种算法复杂度要低于线性算法,所以效率比线性算法高。

3.1.使用heapq优化图书管理功能

1.添加图书到堆数据结构

用heapq模块重新实现add_book函数,这次的队列(queue参数)本身还是个普通列表,但我们不能像原来那样使用list.append方法添加新元素,而是使用heappush函数添加元素。而且也不需要在队列上调用list.sort方法排序了。

如果直接使用原来的Book类,添加数据会报错,这是因为heapq模块规定,添加到优先队列里面的元素必须是可比较的,并且要具备自然顺序,而目前的Book类还不满足这一要求。

python内置的functools模块里有个名为total_ordering的类修饰器,只需要用它修饰Book,并把描述小于关系在魔法函数lt中定义出来。

这样实现之后,就可以通过add_bool函数正常向优先级队列中添加记录了。

from heapq import heappushdef add_book(queue, book):heappush(queue, book)import functools@functools.total_ordering
class Book:def __init__(self, title, due_date):self.title = titleself.due_date = due_date# 判断概述的归还日期是不是比另一本书早def __lt__(self, other):return self.due_date < other.due_date# Example 14
queue = []
add_book(queue, Book('Pride and Prejudice', '2019-06-01'))
add_book(queue, Book('The Time Machine', '2019-05-30'))
add_book(queue, Book('Crime and Punishment', '2019-06-06'))
add_book(queue, Book('Wuthering Heights', '2019-06-12'))
print([b.title for b in queue])

运行上面的代码,图书添加到列表了,并且它的顺序是按照还书日期由远到近排序。

['The Time Machine', 'Pride and Prejudice', 'Crime and Punishment', 'Wuthering Heights']

2.归还图书从堆中删除元素

使用heappop删除堆中的元素,完成归还图书功能。

from heapq import heappop# 判断是否有未归还的图书
def next_overdue_book(queue, now):if queue:book = queue[0]           # Most overdue firstif book.due_date < now:# 使用heappop弹出元素heappop(queue)        # Remove the overdue bookreturn bookraise NoOverdueBooks'''找出预期还没有归还的书,知道全部图书归还'''# Example 18
now = '2019-06-02'book = next_overdue_book(queue, now)
print(book.title)book = next_overdue_book(queue, now)
print(book.title)try:next_overdue_book(queue, now)
except NoOverdueBooks:pass          # Expected
else:assert False  # Doesn't happen

运行上面的代码,输出了预期未归还的图书

The Time Machine
Pride and Prejudice

3.2.测试heapq队列添加和移除元素性能

def heap_overdue_benchmark(count):def prepare():to_add = list(range(count))random.shuffle(to_add)return [], to_adddef run(queue, to_add):for i in to_add:heappush(queue, i)while queue:heappop(queue)tests = repeat(setup='queue, to_add = prepare()',stmt=f'run(queue, to_add)',globals=locals(),repeat=100,number=1)return print_results(count, tests)# Example 20
baseline = heap_overdue_benchmark(500)
for count in (1_000, 1_500, 2_000):print()comparison = heap_overdue_benchmark(count)print_delta(baseline, comparison)

运行上面的代码,从结果中可以看出消耗的时间比普通的列表快的多

Count   500 takes 0.000172sCount 1,000 takes 0.000345s2.0x data size,  2.0x timeCount 1,500 takes 0.000522s3.0x data size,  3.0x timeCount 2,000 takes 0.000946s4.0x data size,  5.5x time

相关内容

热门资讯

AI做攻略、“双向奔赴”……国... 本文转自:人民网人民网记者 许维娜今年国庆假期,AI(人工智能)成了不少年轻人的“旅行搭子”。行前做...
合肥的这个“梗”, 翻篇了 社交平台的评论区,曾长期流传一句专属于合肥的黑色幽默。有人问合肥哪里好玩,本地网友半开玩笑地回复:合...
7万+旅游中高层都在的群,今天... 做旅游这行,资源卡位、渠道互通、项目落地——往往就差一个“对的机会”。我们的行业社群已聚集70,00...
一场帐篷音乐节,如何让井研从“... 帐篷音乐节现场封面新闻记者 周洪攀10月的研溪湿地,晚风里裹着柑橘的清甜与民谣的回响。夜幕降临,10...
白鹤翩翩落星愿,上海迪士尼度假... 10月9日,世界候鸟日前夕,上海迪士尼度假区星愿公园的湖风里多了一分白鹤的灵动。三座白鹤雕塑正式揭幕...
成都锦江边赏初秋枫叶飘飘 目前,成都锦江边国华街枫叶树红了。近年来,国华街附近的几条街都栽种了红枫树,是城区较集中看枫叶的点位...
GBTA:商务旅行正在复苏,但... 亚太正在成为全球商务旅行增长的重要引擎。在经历疫情后的快速反弹之后,全球商务旅行正在进入一个新的增长...
从景区焕新到节赛引流 邹城双节... 齐鲁网·闪电新闻10月9日讯 中秋、国庆“双节”假期,邹城市文化和旅游局提前谋划、周密部署,立足自身...
江山如此多娇 且看石楼今朝 金秋十月,黄河岸畔秋意盎然,丰收盛会喜迎国庆。本次石楼国庆文旅活动以“江山如此多娇,且看石楼今朝”为...
海南国庆假期接待游客475.4... 2026年国庆假期,海南接待游客475.40万人次,较2025年增长13.1%,较2024年增长15...