1. 程式人生 > >資料結構與算法系列7--佇列

資料結構與算法系列7--佇列

什麼是佇列?

1.先進者先出,這就是典型的“佇列”結構。
2.支援兩個操作:入隊enqueue(),放一個數據到隊尾;出隊dequeue(),從隊頭取一個元素。所以,和棧一樣,佇列也是一種操作受限的線性表。
佇列的應用也非常廣泛,特別是一些具有某些額外特性的佇列,比如迴圈佇列、阻塞佇列、併發佇列。它們在很多偏底層系統、框架、中介軟體的開發中,起著關鍵性的作用。

實現佇列的兩種方式?

順序佇列和鏈式佇列
跟棧一樣,佇列可以用陣列來實現,也可以用連結串列來實現。用陣列實現的棧叫作順序棧,用連結串列實現的棧叫作鏈式棧。同樣,用陣列實現的佇列叫作順序佇列,用連結串列實現的佇列叫作鏈式佇列。
基於陣列實現佇列:

'''基於陣列實現佇列'''
class ArrayQueue:
    def __init__(self,n):
        self.items=[]
        self.n=n    #其中n為指定的陣列容量
        self.head=0#頭指標
        self.tail=0#尾指標

    def enqueue(self,data):
        if self.tail==self.n:
            if self.head==0:
                 #佇列已經滿了
                return False
            else:
                #需要做資料的搬移處理,因為此時陣列前面還有空間
                for i in range(0,self.tail-self.head):
                    self.items[i]=self.items[self.head+i]

                self.tail=self.tail-self.head #重新指定尾指標的 位置
                self.head=0#頭指標重新指向首位

        self.items.insert(self.tail,data)
        self.tail=self.tail+1
        return True

    def dequeue(self):
        if self.head!=self.tail:
            data=self.items[self.head]#獲取出隊的值
            self.head=self.head+1#頭指標指向下個位置
            return data
        else:
            return False

    def __repr__(self):
        return " ".join(i for i in self.items[self.head:self.tail])




print("----------")
queue=ArrayQueue(10)
for i in range(5):
    queue.enqueue(str(i))

for i in range(6):
    print(queue.dequeue())
    print(queue)

基於連結串列實現佇列:

'''基於連結串列實現佇列'''
class Node():
    def __init__(self,data,next=None):
        self.data=data
        self.next=next


class LinkedQueue():
    def __init__(self):
        #定義頭指標和為指標,分別對應出隊口和入隊口
        self.head=None
        self.tail=None

    #入隊函式
    def enqueue(self,data):
        node= Node(data)
        if self.tail:
            self.tail.next=node #插入連結串列末尾
        else:
            self.head=node      #如果此時連結串列為空,頭指標指向當前插入節點

        self.tail=node  #讓尾指標指向尾節點
        return True

    #出隊函式
    def dequeue(self):
        if self.head:
            data=self.head.data
            self.head=self.head.next#頭指標向後移動

            if not self.head:
                self.tail=None
            return data #返回出隊的值
        else:
            return False


    def __repr__(self):
        data=[]
        current=self.head
        while current:
            data.append(current.data)
            current=current.next

        return "->".join(value for value in data)


if __name__=="__main__":
    queue=LinkedQueue()
    for i in range(5):
        queue.enqueue(str(i))

    print(queue)
    print("----------")
    for i in range(6):
        print(queue.dequeue())
        print(queue)

兩者的區別
基於連結串列的實現方式,可以實現一個支援無限排隊的無界佇列,但是可能會導致過多的請求排隊等待,請求處理的響應時間過長。所以,針對響應時間比較敏感的系統,基於連結串列實現的無限排隊的執行緒池是不合適的。
而基於陣列實現的有界佇列,佇列的大小有限,所以執行緒池中排隊的請求超過佇列大小時,接下來的請求就會被拒絕,這種方式對響應時間敏感的系統來說,就相對更加合理。不過,設定一個合理的佇列大小,也是非常有講究的。佇列太大導致等待的請求太多,佇列太小會導致無法充分利用系統資源、發揮最大效能。

佇列有哪些常見的應用

1.阻塞佇列
1)在佇列的基礎上增加阻塞操作,就成了阻塞佇列。
2)阻塞佇列就是在佇列為空的時候,從隊頭取資料會被阻塞,因為此時還沒有資料可取,直到佇列中有了資料才能返回;如果佇列已經滿了,那麼插入資料的操作就會被阻塞,直到佇列中有空閒位置後再插入資料,然後在返回。
2.併發佇列
1)在多執行緒的情況下,會有多個執行緒同時操作佇列,這時就會存線上程安全問題。能夠有效解決執行緒安全問題的佇列就稱為併發佇列。
2)併發佇列簡單的實現就是在enqueue()、dequeue()方法上加鎖,但是鎖粒度大併發度會比較低,同一時刻僅允許一個存或取操作。
3)實際上,基於陣列的迴圈佇列利用CAS原子操作,可以實現非常高效的併發佇列。這也是迴圈佇列比鏈式佇列應用更加廣泛的原因。
3.執行緒池資源枯竭時的處理
佇列可以應用在任何有限資源池中,用於排隊請求,比如資料庫連線池等。實際上,對於大部分資源有限的場景,當沒有空閒資源時,基本上都可以通過“佇列”這種資料結構來實現請求排隊。

執行緒池沒有空閒執行緒時,新的任務請求執行緒資源時,執行緒池該如何處理?各種處理策略又是如何實現的呢?

我們一般有兩種處理策略。第一種是非阻塞的處理方式,直接拒絕任務請求;另一種是阻塞的處理方式,將請求排隊,等到有空閒執行緒時,取出排隊的請求繼續處理。