前言
在上一篇博客中我提到了线性数据结构,并且讲了第一种线性数据结构栈的特点以及代码实现,那今天就继续来讲讲队列。
今天的主角:队列
队列是有序集合,添加操作发生在“尾部”,移除操作则发生在“头部”。新元素从尾部进入队列,然后一直向前移动到头部,直到成为下一个被移除的元素。最新添加的元素必须在队列的尾部等待,在队列中时间最长的元素则排在最前面。这种排序原则被称作 FIFO(first-in first-out),即先进先出。
在日常生活中,我们经常排队,这便是最简单的队列例子。进电影院要排队,在超市结账要排队,买咖啡也要排队(等着从盘子栈中取盘子)。好的队列只允许一头进,另一头出,不可能发生插队或者中途离开的情况;计算机科学中也有众多的队列例子。假如计算机实验室有 30 台计算机,它们都与同一台打印机相连。当学生需要打印的时候,他们的打印任务会进入一个队列。该队列中的第一个任务就是即将执行的打印任务。如果一个任务排在队列的最后面,那么它必须等到前面的任务都执行完毕后才能执行。操作系统使用一些队列来控制计算机进程。调度机制往往基于一个队列算法,其目标是尽可能快地执行程序,同时服务尽可能多的用户。在打字时,我们有时会发现字符出现的速度比击键速度慢。这是由于计算机正在做其他的工作。击键操作被放入一个类似于队列的缓冲区,以便对应的字符按正确的顺序显示。
队列抽象数据类型
- Queue()创建一个空队列。它不需要参数,且会返回一个空队列。
- enqueue(item)在队列的尾部添加一个元素。它需要一个元素作为参数,不返回任何值。
- dequeue()从队列的头部移除一个元素。它不需要参数,且会返回一个元素,并修改队列的内容。
- isEmpty()检查队列是否为空。它不需要参数,且会返回一个布尔值。
- size()返回队列中元素的数目。它不需要参数,且会返回一个整数。
下面来看看它的python代码实现:
1.队列的python实现
# 这里我们用列表头入队,列表尾出队
class Queue:
def __init__(self):
self.items=[]
def isEmpty(self):
return '队列为空' if self.items==[] else'队列不为空'
def enqueue(self,item):
self.items.insert(0,item)
def dequeue(self):
return self.items.pop()
def size(self):
return len(self.items)
q=Queue()
print(q.isEmpty())
q.enqueue('1')
q.enqueue('abc')
q.enqueue(23)
print(q.isEmpty())
print(q.size())
print(q.dequeue())
2.队列的应用
利用队列解决问题1:著名的约瑟夫斯问题
相传,约瑟夫斯当年和 39 个战友在山洞中对抗罗马军队。眼看着即将失败,他们决定舍生取义。于是,他们围成一圈,从某个人开始,按顺时针方向杀掉第 7 人。约瑟夫斯同时也是卓有成就的数学家。据说,他立刻找到了自己应该站的位置,从而使自己活到了最后。
from pythonds.basic import Queue
def solve(personlist,num):
q=Queue()
for person in personlist:
q.enqueue(person)
while q.size()>1:
for i in range(num):
q.enqueue(q.dequeue())
q.dequeue()
return q.dequeue()
print(solve([i for i in range(40)],7))
利用队列解决问题2:打印机问题
考虑计算机科学实验室里的这样一个场景:在任何给定的一小时内,实验室里都有约 10 个学生。他们在这一小时内最多打印 2 次,并且打印的页数从 1 到 20 不等。实验室的打印机比较老旧,每分钟只能以低质量打印 10 页。可以将打印质量调高,但是这样做会导致打印机每分钟只能打印 5 页。降低打印速度可能导致学生等待过长时间。那么,应该如何设置打印速度呢?
思路
- 创建一个打印任务队列。每一个任务到来时都会有一个时间戳。一开始,队列是空的。
- 针对每一秒(currentSecond),执行以下操作。
是否有新创建的打印任务?如果是,以 currentSecond 作为其时间戳并将该任务加入到队列中。如果打印机空闲,并且有正在等待执行的任务,执行以下操作:
- 从队列中取出第一个任务并提交给打印机;
- 用 currentSecond 减去该任务的时间戳,以此计算其等待时间;
- 将该任务的等待时间存入一个列表,以备后用;
- 根据该任务的页数,计算执行时间。
- 打印机进行一秒的打印,同时从该任务的执行时间中减去一秒。
- 如果打印任务执行完毕,或者说任务需要的时间减为 0,则说明打印机回到空闲状态。
- 当模拟完成之后,根据等待时间列表中的值计算平均等待时间
import random
from pythonds.basic import Queue
class Printer:
def __init__(self,ppm):
self.pagerate=ppm
self.currentTask=None
self.timeRemaining=0
def tick(self):
if self.currentTask != None:
self.timeRemaining=self.timeRemaining-1
if self.timeRemaining<=0:
self.currentTask=None
def busy(self):
if self.currentTask!=None:
return True
else:
return False
def startNext(self,newtask):
self.currentTask=newtask
self.timeRemaining=newtask.getPages()*60/self.pagerate
class Task:
def __init__(self,time):
self.timestamp=time
self.pages=random.randrange(1,21)
def getStamp(self):
return self.timestamp
def getPages(self):
return self.pages
def waitTime(self,currenttime):
return currenttime-self.timestamp
def newPrintTask():
num=random.randrange(1,181)
# 每小时20个任务,相当于180s一个任务
if num==180:
return True
else:
return False
def simulation(numSeconds,pagesPerMinute):
labprinter=Printer(pagesPerMinute)
printqueue=Queue()
waitingtimes=[]
for currentSecond in range(numSeconds):
if newPrintTask():
task=Task(currentSecond)
printqueue.enqueue(task)
if (not labprinter.busy()) and (not printqueue.isEmpty()):
nexttask=printqueue.dequeue()
waitingtimes.append(nexttask.waitTime(currentSecond))
labprinter.startNext(nexttask)
labprinter.tick()
averageWait=sum(waitingtimes)/len(waitingtimes)
print(f"平均等待时间:{averageWait}s,剩余{printqueue.size()}件工作")
for i in range(10):
simulation(3600,5)
print("--------------------------")
for i in range(10):
simulation(3600,10)
可以看到还是每分钟十页比较好。
一个新的朋友:双端队列
双端队列(deque,全名double-ended queue) 是与队列类似的有序集合。它有一前、一后两端,元素在其中保持自己的位置。与队列不同的是,双端队列对在哪一端添加和移除元素没有任何限制。新元素既可以被添加到前端,也可以被添加到后端。同理,已有的元素也能从任意一端移除。某种意义上,双端队列是栈和队列的结合。
双端队列抽象数据类型
- Deque() 创建一个空的双端队列
- addfront(item) 从队头加入一个item元素
- addrear(item) 从队尾加入一个item元素
- removefront() 从队头删除一个item元素
- removerear() 从队尾删除一个item元素
- isEmpty() 判断双端队列是否为空
- size() 返回队列的大小
1.双端队列的python实现
class Deque(object):
"""双端队列"""
def __init__(self):
self.items = []
def isEmpty(self):
"""判断队列是否为空"""
return self.items == []
def addfront(self, item):
"""在队头添加元素"""
self.items.insert(0,item)
def addrear(self, item):
"""在队尾添加元素"""
self.items.append(item)
def removefront(self):
"""从队头删除元素"""
return self.items.pop(0)
def removerear(self):
"""从队尾删除元素"""
return self.items.pop()
def size(self):
"""返回队列大小"""
return len(self.items)
deque = Deque()
deque.addfront(1)
deque.addfront(2)
deque.addrear(3)
deque.addrear(4)
print(deque.items)
print(deque.size())
print(deque.removefront())
print(deque.removefront())
print(deque.removerear())
print(deque.removerear())
2.双端队列的应用
用双端队列解决回文问题: 回文是指从前往后读和从后往前读都一样的字符串,例如 radar、toot,以及 madam。
from pythonds.basic import Deque
def palchecker(aString):
chardeque = Deque()
for ch in aString:
chardeque.addRear(ch)
stillEqual = True
while chardeque.size() > 1 and stillEqual:
first = chardeque.removeFront()
last = chardeque.removeRear()
if first != last:
stillEqual = False
return stillEqual
print(palchecker('toot'))
print(palchecker('radar'))
print(palchecker('ndasjkfbjka'))
日常结尾吐槽
唉,JAVA web真难,也不是难吧就是之前上课没好好听,然后最近该看的网课视频也耽误了没看。一个简单的MVC例子,明明原理都明白,但是写起来总感觉这里一点那里一点没搞清楚,感觉今天一下午写例子的时候其实已经有点感觉,等我周末去补补习应该就问题不大了。最近要学的,要复习的,要做的太多,每天很累,但是还是要坚持!!!(然后这篇博客也没怎么太用心写,这里小声道个歉,对不起)