一、队列
队列列Queue是一种有次序的数据集合,其特征是
- 新数据项的添加总发生在一端(通常称为“尾rear”端),而现存数据项的移除总发生在另一端(通常称为“首front”端)
- 当数据项加入队列,首先出现在队尾,随着队首数据项的移除,它逐渐接近队首。
- 新加入的数据项必须在数据集末尾等待,而等待时间最长的数据项则是队首。这种次序安排的原则称为(FIFO:First-in first-out)先进先出,或“先到先服务first-come first-served”
- 队列仅有一个入口和一个出口不允许数据项直接插入队中,也不允许从中间移除数据项
队列的例子:排队,打印队列,进程调度,键盘缓冲
抽象数据类型Queue
方法名 | 描述 | 返回值 |
---|---|---|
Queue() | 创建一个空队列对象 | Queue对象 |
enqueue(item) | 将数据项item添加到队尾 | 无返回值 |
dequeue() | 从队首移除数据项,队列被修改 | 队首数据项 |
isEmpty() | 测试是否为空队列 | 布尔值 |
size() | 返回队列中数据项的个数 | 数据项个数(整数) |
Python实现ADT Queue
- 队首是列表的末端;队尾是列表的首端。也可以倒过来。
class Queue:
def __init__(self):
self.items = []
def enqueue(self, item):
self.items.insert(0, item)
def dequeue(self):
return self.items.pop()
def is_empty(self):
return len(self.items) == 0
def size(self):
return len(self.items)
二、队列的应用-约瑟夫问题
约瑟夫问题
- 传说犹太人反叛罗马人,落到困境,约瑟夫和39人决定坐成一圈儿,报数1~7,报到7的人淘汰,结果约瑟夫给自己安排了个位置,最后活了下来……
- 我们用传递热土豆来模拟
- 模拟程序采用队列来存放所有参加游戏的人名,游戏时,队首始终是持有土豆的人
- 模拟游戏开始,只需要将队首的人出队,随即再到队尾入队,算是土豆的一次传递
- 土豆每传递7次,将队首的人移除,不再入队如此反复,直到队列中剩余1人
def josephus(name_list, num):
# 在name_list人中,每数到num的人就会被淘汰
q = Queue()
for i in name_list:
q.enqueue(i)
while q.size() > 1:
for j in range(num):
q.enqueue(q.dequeue()) # 一次传递
q.dequeue()
return q.dequeue()
name_list = [x for x in range(1, 41)]
print(josephus(name_list, 7)) # 40个人游戏,每数到7的人淘汰
### 输出结果
6
三、队列应用-打印任务
多人共享一台打印机,采取“先到先服务”的队列策略来执行打印任务
- 这种打印作业系统的容量有多大?在能够接受的等待时间内,系统能容纳多少用户以多高频率提交多少打印任务?
- 一个具体的实例配置如下:一个实验室,在任意的一个小时内,大约有10名学生在场,这一小时中,每人会发起2次左右的打印,每次1~20页
- 打印机的性能是:以草稿模式打印的话,每分钟10页,以正常模式打印的话,打印质量好,但速度下降
为每分钟5页。 - 问题是:怎么设定打印机的模式,让大家都不会等太久的前提下尽量提高打印质量?——这是一个典型的决策支持问题,但无法通过规则直接计算
问题建模
- 首先对问题进行抽象,确定相关的对象和过程。抛弃那些对问题实质没有关系的学生性别、年龄、打印机型号、打印内容、纸张大小等等众多细节。
- 对象
- 打印任务的属性:提交时间、打印页数
- 打印队列的属性:具有FIFO性质的打印任务队列
- 打印机的属性:打印速度、是否忙
- 过程
- 确定生成概率:实例为每小时会有10个学生提交的20个作业,这样,概率是每180秒会有1个作业生成并提交,概率为每秒1/180。
- 确定打印页数:实例是1~20页,那么就是1~20页之间概率相同。
- 当前的打印作业:正在打印的作业。
- 打印结束倒计时:新作业开始打印时开始倒计时,回0表示打印完毕,可以处理下一个作业。
- 模拟时间
- 统一的时间框架:以最小单位(秒)均匀流逝的时间,设定结束时间
- 同步所有过程:在一个时间单位里,对生成打印任务和实施打印两个过程各处理一次
打印任务模拟程序
- 时间按照秒的单位流逝。按照概率生成打印作业,加入打印队列。
- 如果打印机空闲,且队列不空,则取出队首作业打印,记录此作业等待时间。
- 如果打印机忙,则按照打印速度进行1秒打印。
- 如果当前作业打印完成,则打印机进入空闲。
- 作业的等待时间:生成作业时,记录生成的时间戳。开始打印时,当前时间减去生成时间即可。
- 作业的打印时间:生成作业时,记录作业的页数。开始打印时,页数除以打印速度即可。
import random
import math
from my_queue import Queue
class Printer:
def __init__(self, ppm):
# 打印机初始状态队列为空,打印任务为空,剩余打印时间为0
self.ppm = ppm
self.current_task: Task = None
self.queue = Queue()
self.time_remaining = 0
def is_busy(self):
return self.current_task != None
def start_next(self, task):
# 打印新作业
self.current_task = task
# 计算该打印任务需要多长时间(秒,向上取整)
self.time_remaining = math.ceil(self.current_task.pages * 60 / self.ppm)
def tick(self):
# 打印1秒
if self.is_busy:
self.time_remaining -= 1
if self.time_remaining <= 0:
self.current_task = None
class Task:
def __init__(self, submit_time):
# 记录打印任务的生成时间戳,每份打印任务页数在1~20之间
self.submit_time = submit_time
self.pages = random.randrange(1, 21)
def wait_time(self, start_time):
return start_time - self.submit_time
def gen_new_task():
# 模拟概率:1/180
return random.randrange(0, 180) == 0
def simulation(total_seconds, ppm):
printer = Printer(ppm=ppm)
waiting_times = []
task_count = 0
# 模拟时间流逝
for current_second in range(total_seconds):
# 按概率生成打印任务
if gen_new_task():
t = Task(submit_time=current_second)
printer.queue.enqueue(t)
task_count += 1
# 若打印队列不为空且打印机空闲,则开始新的打印任务,并记录该打印任务的已等待时间
if not printer.queue.is_empty() and not printer.is_busy():
next_task: Task = printer.queue.dequeue()
waiting_times.append(next_task.wait_time(current_second))
printer.start_next(next_task)
# 时间流逝1秒
printer.tick()
unfinish_count = printer.queue.size()
finish_count = task_count - unfinish_count
average_wait = sum(waiting_times) / len(waiting_times)
print(
f"一共{task_count}个打印任务,已完成{finish_count}个,未完成{unfinish_count}个,平均等待时间:{average_wait:.2f}"
)
return average_wait
# 模拟运行10次,设定时间3600秒,PPM=5
list1 = []
for i in range(10):
list1.append(simulation(total_seconds=3600, ppm=5))
average = sum(list1) / len(list1)
max_wait = max(list1)
print(f"\n —— 总平均等待时间:{average:.2f}秒,最长下平均等待时间:{max_wait:.2f}秒")
### 输出结果
一共17个打印任务,已完成17个,未完成0个,平均等待时间:108.35
一共24个打印任务,已完成24个,未完成0个,平均等待时间:389.29
一共24个打印任务,已完成24个,未完成0个,平均等待时间:206.71
一共13个打印任务,已完成12个,未完成1个,平均等待时间:4.50
一共23个打印任务,已完成23个,未完成0个,平均等待时间:183.57
一共21个打印任务,已完成21个,未完成0个,平均等待时间:178.38
一共22个打印任务,已完成22个,未完成0个,平均等待时间:27.27
一共17个打印任务,已完成16个,未完成1个,平均等待时间:38.25
一共12个打印任务,已完成12个,未完成0个,平均等待时间:28.17
一共26个打印任务,已完成26个,未完成0个,平均等待时间:209.35
—— 总平均等待时间:137.38秒,最长下平均等待时间:389.29秒
当把PPM设置为10时,模拟结果如下
一共19个打印任务,已完成19个,未完成0个,平均等待时间:19.89
一共29个打印任务,已完成29个,未完成0个,平均等待时间:18.86
一共15个打印任务,已完成15个,未完成0个,平均等待时间:7.53
一共20个打印任务,已完成20个,未完成0个,平均等待时间:12.70
一共14个打印任务,已完成14个,未完成0个,平均等待时间:24.00
一共19个打印任务,已完成19个,未完成0个,平均等待时间:23.68
一共18个打印任务,已完成18个,未完成0个,平均等待时间:18.44
一共14个打印任务,已完成14个,未完成0个,平均等待时间:15.93
一共14个打印任务,已完成14个,未完成0个,平均等待时间:17.86
一共18个打印任务,已完成18个,未完成0个,平均等待时间:7.06
—— 总平均等待时间:16.60秒,最长下平均等待时间:24.00秒
模拟系统/程序对现实的仿真
- 在不耗费现实资源的情况下——有时候真实的实验是无法进行的
- 可以以不同的设定,反复多次模拟来帮助我们进行决策
- 例如:打印任务模拟程序还可以加进不同设定,来进行更丰富的模拟。
- 学生数量加倍了会怎么样?
- 如果学生能接受更长等待时间,会怎么样?
- 打印的页数减少了,会怎么样?
您正在阅读的是《数据结构与算法Python版》专栏!关注不迷路~~