学点算法之队列的学习及应用

时间:2022-05-07
本文章向大家介绍学点算法之队列的学习及应用,主要内容包括约瑟夫问题、队列是什么、队列的Python算法实现、如何活到最后、其他解法、基本概念、基础应用、原理机制和需要注意的事项等,并结合实例形式分析了其使用技巧,希望通过本文能帮助到大家理解应用这部分内容。

约瑟夫问题

约瑟夫问题

有 n 个囚犯站成一个圆圈,准备处决。首先从一个人开始,越过k-2个人(因为第一个人已经被越过),并杀掉第k个人。接着,再越过 k-1个人,并杀掉第k个人。这个过程沿着圆圈一直进行,直到最终只剩下一个人留下,这个人就可以继续活着。

问题是,给定了n和k,一开始要站在什么地方才能避免被处决?

这个问题是以弗拉维奥·约瑟夫命名的,它是1世纪的一名犹太历史学家。他在自己的日记中写道,他和他的40个战友被罗马军队包围在洞中。他们讨论是自杀还是被俘,最终决定自杀,并以抽签的方式决定谁杀掉谁。约瑟夫斯和另外一个人是最后两个留下的人。约瑟夫斯说服了那个人,他们将向罗马军队投降,不再自杀。约瑟夫斯把他的存活归因于运气或天意,他不知道是哪一个

队列是什么

这道题有多种解法,这里先不说别的,要引出今天的主角——队列。队列的定义很好理解:

队列是项的有序结合,其中添加新项的一端称为队尾,移除项的一端称为队首。当一个元素从队尾进入队列时,一直向队首移动,直到它成为下一个需要移除的元素为止。

队列抽象数据类型由以下结构和操作定义。如上所述,队列被构造为在队尾添加项的有序集合,并且从队首移除。队列保持 FIFO 排序属性。 队列操作如下。

  • Queue() 创建一个空的新队列。 它不需要参数,并返回一个空队列。
  • enqueue(item) 将新项添加到队尾。 它需要 item 作为参数,并不返回任何内容。
  • dequeue() 从队首移除项。它不需要参数并返回 item。 队列被修改。
  • isEmpty() 查看队列是否为空。它不需要参数,并返回布尔值。
  • size() 返回队列中的项数。它不需要参数,并返回一个整数。

队列的Python算法实现

为了实现队列抽象数据类型创建一个新类

pythonds/basic/queue.py

class Queue:
    def __init__(self):
        self.items = []

    def isEmpty(self):
        return self.items == []

    def enqueue(self, item):
        self.items.insert(0,item)

    def dequeue(self):
        return self.items.pop()

    def size(self):
        return len(self.items)

想明白了其实就是对 list 的简单操作

如何活到最后

那我们回到上面的问题,如果是你,你要如何选择并活到最后呢?

我们的程序将输入名称列表和一个称为 num 常量用于报数。它将返回以 num 为单位重复报数后剩余的最后一个人的姓名。

假设第一个人是a。从他开始计数,a将先出列再入队列,把他放在队列的最后。经过 num 次的出队入队后,前面的人将被永久移除队列。并且另一个周期开始,继续此过程,直到只剩下一个名字(队列的大小为 1)。

from pythonds.basic.queue import Queue


def hotPotato(namelist, num):
    simqueue = Queue()
    for name in namelist:
        simqueue.enqueue(name)

    while simqueue.size() > 1:
        for i in range(num):
            simqueue.enqueue(simqueue.dequeue())
        simqueue.dequeue()

    return simqueue.dequeue()


print(hotPotato(['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'], 7))

# output: f

其他解法

比较简单的做法是用循环单链表模拟整个过程,时间复杂度是O(n*m)。如果只是想求得最后剩下的人,则可以用数学推导的方式得出公式。先看看模拟过程的解法。

# -*- coding: utf-8 -*- 
class Node(object):
    def __init__(self, value):
        self.value = value 
        self.next = None

def create_linkList(n):
    head = Node(1)
    pre = head
    for i in range(2, n+1):
        newNode = Node(i)
        pre.next= newNode
        pre = newNode
    pre.next = head
    return head

n = 5 #总的个数
m = 2 #数的数目

if m == 1: #如果是1的话,特殊处理,直接输出
    print(n)  
else:
    head = create_linkList(n)
    pre = None
    cur = head
    while cur.next != cur: #终止条件是节点的下一个节点指向本身
        for i in range(m-1):
            pre =  cur
            cur = cur.next
        print(cur.value)
        pre.next = cur.next
        cur.next = None
        cur = pre.next
    print(cur.value)

作业

假设实验室里有一台打印机供学生共性。当学生向共享打印机发送打印任务时,任务被放置在队列中以便以先来先服务的方式被处理。如何才能通过python程序模拟的方式得到每次提交任务的平均等待时间呢?(平均等待时间不包括打印本身的时间,仅指在队列中排队的时间。) 我们假定:

  • 学生们每次打印的页数在1到20页之间。
  • 打印机平均每小时会收到20个打印请求,即平均每180秒1个请求。
  • 每秒新增任务的可能性相等,即任务的产生为独立同分布
  • 打印机的打印速度恒定。

挖坑,要一起来填吗?