首頁 > 軟體

Python常用佇列全面詳細梳理

2023-01-22 14:01:08

一,佇列

和棧一樣,佇列是一種特殊的線性表,特殊之處在於它只允許在表的前端(front)進行刪除操作,而在表的後端(rear)進行插入操作。佇列是一種操作受限制的線性表,進行插入操作的端稱為隊尾,進行刪除操作的端稱為隊頭。佇列中沒有元素時,稱為空佇列。

二,常見佇列

1,FIFO佇列

基本FIFO佇列 先進先出 FIFO即First in First Out,先進先出。

呼叫queue.Queue

from queue import Queue
fifo_queue = Queue()
fifo_queue.put(1)  # 隊尾插入新元素
fifo_queue.put(2)
fifo_queue.put(3)
print(fifo_queue.queue)
print(fifo_queue.get())  # 隊頭取出元素
print(fifo_queue.queue)

連結串列實現

class LNode(object):
    def __init__(self, item, next_=None):
        self.item = item
        self.next = next_
class FIFOQueue(object):
    def __init__(self):
        """初始化"""
        self.head = None
        self.rear = None
    def is_empty(self):
        """判斷是否為空"""
        return self.head is None
    def size(self):
        """獲取佇列長度"""
        cur = self.head
        count = 0
        while True:
            count += 1
            if cur == self.rear:
                break
            cur = cur.next
        return count
    def travel(self):
        """遍歷佇列"""
        if self.is_empty():
            print('queue is empty')
            return
        else:
            cur = self.head
            while True:
                print(cur.item, end='')
                if cur.next:
                    print(',', end='')
                if cur == self.rear:
                    break
                cur = cur.next
            print('')
    def push(self, val):
        """隊尾插入新元素"""
        p = LNode(val)
        if self.is_empty():
            self.head = p
            self.rear = p
        else:
            self.rear.next = p
            self.rear = self.rear.next
    def get(self):
        """獲取隊頭元素"""
        if self.is_empty():
            print('queue is empty')
            return
        else:
            e = self.head.item
            self.head = self.head.next
            return e
if __name__ == '__main__':
    FIFOQueue = FIFOQueue()
    FIFOQueue.push(1)
    FIFOQueue.push(2)
    FIFOQueue.push(3)
    FIFOQueue.push(4)
    FIFOQueue.travel()  # 1,2,3,4
    print(FIFOQueue.get())  # 1
    print(FIFOQueue.get())  # 2
    FIFOQueue.travel()  # 3,4

list實現

# list 實現
class FIFOQueue(object):
    def __init__(self):
        self.queue = list()
    def size(self):
        return len(self.queue)
    def travel(self):
        print(self.queue)
    def push(self, val):
        self.queue.append(val)
    def get(self):
        return self.queue.pop(0)
if __name__ == '__main__':
    FIFOQueue = FIFOQueue()
    FIFOQueue.push(1)
    FIFOQueue.push(2)
    FIFOQueue.push(3)
    FIFOQueue.push(4)
    FIFOQueue.travel()  # 1,2,3,4
    print(FIFOQueue.get())  # 1
    print(FIFOQueue.get())  # 2
    FIFOQueue.travel()  # 3,4

2,LIFO佇列

LIFO即Last in First Out,後進先出。與棧的類似,在隊尾進行插入和刪除操作。

呼叫queue.LifoQueue

from queue import LifoQueue
lifo_queue = LifoQueue()
lifo_queue.put(1)  # 隊尾插入新元素
lifo_queue.put(2)
lifo_queue.put(3)
print(lifo_queue.queue)
print(lifo_queue.get())  # 隊尾取出元素
print(lifo_queue.queue)

連結串列實現

將連結串列頭部作為佇列尾部,在連結串列頭部進行插入和刪除操作。

class LNode(object):
    def __init__(self, item, next_=None):
        self.item = item
        self.next = next_
class LIFOQueue(object):
    def __init__(self):
        """初始化"""
        self.head = None
    def is_empty(self):
        """判斷是否為空"""
        return self.head is None
    def size(self):
        """獲取佇列長度"""
        cur = self.head
        count = 0
        while cur:
            count += 1
            cur = cur.next
        return count
    def travel(self):
        """遍歷佇列"""
        travel_list = []
        cur = self.head
        while cur:
            travel_list.append(cur.item)
            cur = cur.next
        travel_list.reverse()
        print(travel_list)
    def push(self, val):
        """頭部插入"""
        self.head = LNode(val, self.head)
    def get(self):
        """獲取隊頭元素"""
        if self.is_empty():
            print('queue is empty')
            return
        else:
            e = self.head.item
            self.head = self.head.next
            return e
if __name__ == '__main__':
    LIFOQueue = LIFOQueue()
    LIFOQueue.push(1)
    LIFOQueue.push(2)
    LIFOQueue.push(3)
    LIFOQueue.push(4)
    LIFOQueue.travel()  # 1,2,3,4
    print(LIFOQueue.get())  # 4
    print(LIFOQueue.get())  # 3
    LIFOQueue.travel()  # 1,2

List實現

# list 實現
class LIFOQueue(object):
    def __init__(self):
        self.queue = list()
    def size(self):
        return len(self.queue)
    def travel(self):
        print(self.queue)
    def push(self, val):
        self.queue.append(val)
    def get(self):
        return self.queue.pop()
if __name__ == '__main__':
    LIFOQueue = LIFOQueue()
    LIFOQueue.push(1)
    LIFOQueue.push(2)
    LIFOQueue.push(3)
    LIFOQueue.push(4)
    LIFOQueue.travel()  # 1,2,3,4
    print(LIFOQueue.get())  # 4
    print(LIFOQueue.get())  # 3
    LIFOQueue.travel()  # 1,2

3,雙向佇列

雙端佇列(deque,全名 double-ended queue),是一種具有佇列和棧的性質的資料結構。雙端佇列中的元素可以從兩端彈出,其限定插入和刪除操作在表的兩端進行。雙端佇列可以在佇列任意一端入隊和出隊。

呼叫collections .deque

collections 是 python 內建的一個集合模組,裡面封裝了許多集合類,其中佇列相關的集合只有一個:deque。

deque 是雙邊佇列(double-ended queue),具有佇列和棧的性質,在 list 的基礎上增加了移動、旋轉和增刪等。

deque(maxlen=3),通過maxlen引數,可以建立固定長度的佇列,當新元素加入佇列且佇列已滿,會自動從另一端移除首個元素。不指定maxlen,得到無界限的佇列。

from collections import deque
d = deque([])
d.append('a')  # 在最右邊新增一個元素,此時 d=deque('a')
print(d)
d.appendleft('b')  # 在最左邊新增一個元素,此時 d=deque(['b', 'a'])
print(d)
d.extend(['c', 'd'])  # 在最右邊新增所有元素,此時 d=deque(['b', 'a', 'c', 'd'])
print(d)
d.extendleft(['e', 'f'])  # 在最左邊新增所有元素,此時 d=deque(['f', 'e', 'b', 'a', 'c', 'd'])
print(d)
d.pop()  # 將最右邊的元素取出,返回 'd',此時 d=deque(['f', 'e', 'b', 'a', 'c'])
print(d)
d.popleft()  # 將最左邊的元素取出,返回 'f',此時 d=deque(['e', 'b', 'a', 'c'])
print(d)
d.rotate(-2)  # 向左旋轉兩個位置(正數則向右旋轉),此時 d=deque(['a', 'c', 'e', 'b'])
print(d)

雙向連結串列實現

class DLNode(object):
    def __init__(self, item, prior_=None, next_=None):
        self.item = item
        self.prior = prior_
        self.next = next_
class DQueue(object):
    def __init__(self):
        self.head = None    # 頭指標
        self.rear = None    # 尾製造
    def is_empty(self):
        return self.head is None
    def length(self):
        if self.is_empty():
            print('queue is empty')
            return
        else:
            cur = self.head
            count = 0
            while True:
                count += 1
                if cur == self.rear:
                    break
                cur = cur.next
            return count
    def travel(self):
        """遍歷佇列"""
        if self.is_empty():
            print('queue is empty')
            return
        else:
            cur = self.head
            while True:
                print(cur.item, end='')
                if cur.next:
                    print(',', end='')
                if cur == self.rear:
                    break
                cur = cur.next
            print('')
    def push_rear(self, val):
        """隊尾插入元素"""
        p = DLNode(val)
        if self.is_empty():
            self.head = p
            self.rear = p
        else:
            self.rear.next = p
            p.prior = self.rear
            self.rear = self.rear.next
    def push_head(self, val):
        """隊頭插入元素"""
        p = DLNode(val)
        if self.is_empty():
            self.head = p
            self.rear = p
        else:
            p.next = self.head
            self.head.prior = p
            self.head = p
    def pop_rear(self):
        """獲取隊尾元素"""
        if self.is_empty():
            print('queue is empty')
            return
        else:
            p = self.rear
            self.rear = self.rear.prior
            self.rear.next = None
            return p.item
    def pop_head(self):
        """獲取隊頭元素"""
        if self.is_empty():
            print('queue is empty')
            return
        else:
            e = self.head.item
            self.head = self.head.next
            return e
if __name__ == '__main__':
    DQueue = DQueue()
    DQueue.push_head(1)
    DQueue.push_head(2)
    DQueue.push_head(3)
    DQueue.travel()  # 3,2,1
    DQueue.push_rear('a')
    DQueue.push_rear('b')
    DQueue.travel()  # 3,2,1,a,b
    print(DQueue.pop_head())  # 3
    print(DQueue.pop_rear())  # b
    print(DQueue.pop_rear())  # a
    DQueue.travel()  # 2,1

list實現

class DQueue:
    """雙端佇列"""
    def __init__(self):
        self.queue = []
    def push_head(self, val):
        """從隊頭加入一個元素"""
        self.queue.insert(0, val)
    def push_rear(self, val):
        """從隊尾加入一個元素"""
        self.queue.append(val)
    def pop_head(self):
        """從隊頭刪除一個元素"""
        return self.queue.pop(0)
    def pop_rear(self):
        """從隊尾刪除一個元素"""
        return self.queue.pop()
    def is_empty(self):
        """是否為空"""
        return self.queue == []
    def size(self):
        """佇列長度"""
        return len(self.queue)
    def travel(self):
        print(self.queue)
if __name__ == "__main__":
    DQueue = DQueue()
    DQueue.push_head(1)
    DQueue.push_head(2)
    DQueue.push_head(3)
    DQueue.travel()  # [3, 2, 1]
    DQueue.push_rear('a')
    DQueue.push_rear('b')
    DQueue.travel()  # [3, 2, 1, 'a', 'b']
    print(DQueue.pop_head())  # 3
    print(DQueue.pop_rear())  # b
    print(DQueue.pop_rear())  # a
    DQueue.travel()  # [2, 1]

4,優先順序佇列

優先順序佇列是一種容器型資料結構,它能管理一隊記錄,並按照排序欄位(例如一個數位型別的權重值)為其排序。由於是排序的,所以在優先順序佇列中你可以快速獲取到最大的和最小的值。

可以認為優先順序佇列是一種修改過的普通佇列:普通佇列依據記錄插入的時間來獲取下一個記錄,而優先順序佇列依據優先順序來獲取下一個記錄,優先順序取決於排序欄位的值。

優先順序佇列常用來解決排程問題,比如給緊急的任務更高的優先順序。以作業系統的任務排程為例:高優先順序的任務(比如實時遊戲)應該先於低優先順序的任務(比如後臺下載軟體更新)執行。

呼叫queue.PriorityQueue

在 Python 中,內建的標準庫提供了兩種實現優先佇列的資料結構,分別是 heapq 模組和 PriorityQueue 模組,

最小優先順序佇列

更小的值具有更高的優先順序,也就是會被最先輸出

# 優先順序佇列
from queue import PriorityQueue as PQ
Pqueue = PQ()
Pqueue.put((1, 'a'))
Pqueue.put((3, 'c'))
Pqueue.put((2, 'b'))
Pqueue.put((2, 'd'))
Pqueue.put((5, 'e'))
print(Pqueue.queue)  # [(1, 'a'), (2, 'd'), (2, 'b'), (3, 'c'), (5, 'e')]
while not Pqueue.empty():
    print(Pqueue.get())
# (1, 'a')
# (2, 'b')
# (2, 'd')
# (3, 'c')
# (5, 'e')

最大優先順序佇列

更大的值具有更高的優先順序,也就是會被最先輸出。

from queue import PriorityQueue as PQ
Pqueue = PQ()
Pqueue.put((-1, 'a'))
Pqueue.put((-3, 'c'))
Pqueue.put((-2, 'b'))
Pqueue.put((-2, 'd'))
Pqueue.put((-5, 'e'))
print(Pqueue.queue)  # [(-5, 'e'), (-3, 'c'), (-2, 'b'), (-1, 'a'), (-2, 'd')]
while not Pqueue.empty():
    print(Pqueue.get())
# (-5, 'e')
# (-3, 'c')
# (-2, 'b')
# (-2, 'd') 當兩個物件的優先順序一致時,按照插入順序排列
# (-1, 'a')

基於 heapq 實現

heapq 涉及到另一種資料結構“堆”,用heapq 實現優先順序佇列,也是基於最小堆,最大堆實現,這些在後面“堆”再一起研究下。

import heapq
class PriorityQueue(object):
    def __init__(self):
        self._queue = []
        # self._index = 0
    def push(self, item, priority):
        """
        佇列由 (priority, index, item) 形式組成
        priority 預設是最小優先順序,增加 "-" 實現最大優先順序,
        index 是為了當兩個物件的優先順序一致時,按照插入順序排列
        """
        heapq.heappush(self._queue, (-priority, item))
        # self._index += 1
    def pop(self):
        """
        彈出優先順序最高的物件
        """
        return heapq.heappop(self._queue)[-1]
    def qsize(self):
        return len(self._queue)
    def empty(self):
        return True if not self._queue else False
if __name__ == '__main__':
    PQueue = PriorityQueue()
    PQueue.push('a', 1)
    PQueue.push('c', 3)
    PQueue.push('b', 2)
    PQueue.push('d', 2)
    PQueue.push('e', 5)
    PQueue.push('f', 1)
    while not PQueue.empty():
        print(PQueue.pop())    # e c b d a f

5,迴圈佇列

在之前實現的佇列時,都為固定佇列長度,都建立無限佇列,當佇列空間有限時,插入和刪除元素會有問題呢?

假定用長度為6的陣列,表示長度為6的佇列。佇列中已經有三個元素a1、a2、a3。

如果新插入元素,只需要在隊尾插入便可,在下標3的位置插入新元素a4,入佇列的時間複雜度O(1)。

如果刪除元素,當a1出佇列後,其後面的a2、a3、a4則需要向前移動一個位置,就好日常排隊時,當前麵人離開,後面的隊伍都往前移動一步,所以出佇列的時間複雜度為O(n)。

這種效率顯然是不可以接受的,那麼能不能不讓所有成員都往前挪一位呢?

所以在原來的基礎上,加入兩個變數front、rear分別儲存隊頭和隊尾的下標。

此時front =0 ,rear = 3。

當有新元素插入隊尾時,rear = rear+1。

當有元素出佇列時,front = front + 1

這樣一來,似乎不將後面所有成員往前挪,只需維護一下front的指向(front += 1)就可以保證隊首,但是,當遇到下面這情況時,就存在“假溢位”的情況。

將a2、a3都出佇列,此時front = 3,在將a6插入佇列,此時rear = 6。

此時,佇列長度為3,佇列未滿,再將a7插入佇列時,就會報錯陣列越界,但是此時陣列空間未滿,前面0、1、2都空著,這種現象稱為“假溢位”。

雖然這種方法不用移動元素,但是卻造成空間上的浪費。可以看出此時陣列是還有空間去容納新元素a7的,因此我們需要將前面浪費的空間重新利用起來,減少空間的浪費,這就是迴圈佇列的意義所在了。

1.迴圈佇列包括兩個指標(其實就是兩個整數型變數,因為在這裡有指示作用,所以這裡理解為指標), front 指標指向隊頭元素, rear 指標指向隊尾元素的下一個位置。

2.rear和front互相追趕著,這個追趕過程就是佇列新增和刪除的過程,如果rear追到head說明佇列滿了,如果front追到rear說明佇列為空。

3,rear和front位置的移動,關鍵在於% (取模運算),這樣就防止rear和front 超過maxsize。

網上最常看到的實現程式碼

class SqQueue(object):
    def __init__(self, maxsize):
        self.queue = [None] * maxsize
        self.maxsize = maxsize
        self.front = 0
        self.rear = 0
    # 返回當前佇列的長度
    def QueueLength(self):
        return (self.rear - self.front + self.maxsize) % self.maxsize
    # 如果佇列未滿,則在隊尾插入元素,時間複雜度O(1)
    def EnQueue(self, data):
        if (self.rear + 1) % self.maxsize == self.front:
            print("The queue is full!")
        else:
            self.queue[self.rear] = data
           # self.queue.insert(self.rear,data)
            self.rear = (self.rear + 1) % self.maxsize
    # 如果佇列不為空,則刪除隊頭的元素,時間複雜度O(1)
    def DeQueue(self):
        if self.rear == self.front:
            print("The queue is empty!")
        else:
            data = self.queue[self.front]
            self.queue[self.front] = None
            self.front = (self.front + 1) % self.maxsize
            return data
    # 輸出佇列中的元素
    def ShowQueue(self):
        for i in range(self.maxsize):
            print(self.queue[i],end=',')
        print(' ')

這有個bug,由於 self.rear = (self.rear + 1) % self.maxsize 這會造成一個空間的浪費!! 可以執行下程式碼看看。

所以自己寫了一段程式碼,直接使用現有元素個數cnt 與 maxsize 比較來判斷是否為空?是否已滿?

class CycleQueue(object):
    def __init__(self, maxsize):
        self.queue = [None] * maxsize
        self.maxsize = maxsize
        self.front = 0
        self.rear = 0
        self.cnt = 0
    def is_empty(self):
        return self.cnt == 0
    def is_full(self):
        return self.cnt == self.maxsize
    def push(self, val):
        if self.is_full():
            print("The queue is full!")
            return
        if self.is_empty():
            self.queue[self.rear] = val
            self.cnt += 1
        else:
            self.rear = (self.rear + 1) % self.maxsize
            self.queue[self.rear] = val
            self.cnt += 1
    def pop(self):
        if self.is_empty():
            print("The queue is empty!")
            return
        val = self.queue[self.front]
        self.queue[self.front] = None
        self.front = (self.front + 1) % self.maxsize
        self.cnt -= 1
        return val
    def travel(self):
        travel_list = [self.queue[(self.front + i) % self.maxsize] for i in range(self.cnt)]
        print(travel_list)
    def size(self):
        return self.cnt
if __name__ == '__main__':
    CycleQueue = CycleQueue(6)
    CycleQueue.push('a1')
    CycleQueue.push('a2')
    CycleQueue.push('a3')
    CycleQueue.push('a4')
    CycleQueue.push('a5')
    CycleQueue.travel()  # ['a1', 'a2', 'a3', 'a4', 'a5']
    CycleQueue.push('a6')
    CycleQueue.travel()  # ['a1', 'a2', 'a3', 'a4', 'a5', 'a6']
    CycleQueue.pop()
    CycleQueue.push('a7')
    CycleQueue.travel()  # ['a2', 'a3', 'a4', 'a5', 'a6', 'a7']
    CycleQueue.pop()
    CycleQueue.pop()
    CycleQueue.push('a8')
    CycleQueue.travel()  # ['a4', 'a5', 'a6', 'a7', 'a8']

到此這篇關於Python常用佇列全面詳細梳理的文章就介紹到這了,更多相關Python常用佇列內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com