python數據結構---3.線性數據結構(隊列)

前言

在上一篇博客中我提到了線性數據結構,並且講了第一種線性數據結構棧的特點以及代碼實現,那今天就繼續來講講隊列。

今天的主角:隊列

隊列是有序集合,添加操作發生在“尾部”,移除操作則發生在“頭部”。新元素從尾部進入隊列,然後一直向前移動到頭部,直到成爲下一個被移除的元素。最新添加的元素必須在隊列的尾部等待,在隊列中時間最長的元素則排在最前面。這種排序原則被稱作 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 頁。降低打印速度可能導致學生等待過長時間。那麼,應該如何設置打印速度呢?

思路

  1. 創建一個打印任務隊列。每一個任務到來時都會有一個時間戳。一開始,隊列是空的。
  2. 針對每一秒(currentSecond),執行以下操作。
    是否有新創建的打印任務?如果是,以 currentSecond 作爲其時間戳並將該任務加入到隊列中。如果打印機空閒,並且有正在等待執行的任務,執行以下操作:
  • 從隊列中取出第一個任務並提交給打印機;
  • 用 currentSecond 減去該任務的時間戳,以此計算其等待時間;
  • 將該任務的等待時間存入一個列表,以備後用;
  • 根據該任務的頁數,計算執行時間。
  • 打印機進行一秒的打印,同時從該任務的執行時間中減去一秒。
  • 如果打印任務執行完畢,或者說任務需要的時間減爲 0,則說明打印機回到空閒狀態。
  1. 當模擬完成之後,根據等待時間列表中的值計算平均等待時間
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例子,明明原理都明白,但是寫起來總感覺這裏一點那裏一點沒搞清楚,感覺今天一下午寫例子的時候其實已經有點感覺,等我週末去補補習應該就問題不大了。最近要學的,要複習的,要做的太多,每天很累,但是還是要堅持!!!(然後這篇博客也沒怎麼太用心寫,這裏小聲道個歉,對不起)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章