前言
在上一篇博客中我提到了線性數據結構,並且講了第一種線性數據結構棧的特點以及代碼實現,那今天就繼續來講講隊列。
今天的主角:隊列
隊列是有序集合,添加操作發生在“尾部”,移除操作則發生在“頭部”。新元素從尾部進入隊列,然後一直向前移動到頭部,直到成爲下一個被移除的元素。最新添加的元素必須在隊列的尾部等待,在隊列中時間最長的元素則排在最前面。這種排序原則被稱作 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例子,明明原理都明白,但是寫起來總感覺這裏一點那裏一點沒搞清楚,感覺今天一下午寫例子的時候其實已經有點感覺,等我週末去補補習應該就問題不大了。最近要學的,要複習的,要做的太多,每天很累,但是還是要堅持!!!(然後這篇博客也沒怎麼太用心寫,這裏小聲道個歉,對不起)