数据结构与算法Python版 队列


一、队列

队列列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版》专栏!关注不迷路~~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值