全網最適合入門的物件導向程式設計教程:41 Python 常用複合資料型別-佇列(FIFO、LIFO、優先順序佇列、雙端佇列和環形佇列)

FreakStudio發表於2024-08-30

全網最適合入門的物件導向程式設計教程:41 Python 常用複合資料型別-佇列(FIFO、LIFO、優先順序佇列、雙端佇列和環形佇列)

image

摘要:

在 Python 中,佇列(Queue)是一種常用的資料結構,用於按照特定的順序儲存和訪問資料。佇列的主要型別包括先進先出(FIFO)、後進先出(LIFO)、優先順序佇列、雙端佇列(Deque)和環形佇列,每種佇列在不同的應用場景中都有其獨特的用途。

原文連結:

FreakStudio的部落格

往期推薦:

學嵌入式的你,還不會物件導向??!

全網最適合入門的物件導向程式設計教程:00 物件導向設計方法導論

全網最適合入門的物件導向程式設計教程:01 物件導向程式設計的基本概念

全網最適合入門的物件導向程式設計教程:02 類和物件的 Python 實現-使用 Python 建立類

全網最適合入門的物件導向程式設計教程:03 類和物件的 Python 實現-為自定義類新增屬性

全網最適合入門的物件導向程式設計教程:04 類和物件的Python實現-為自定義類新增方法

全網最適合入門的物件導向程式設計教程:05 類和物件的Python實現-PyCharm程式碼標籤

全網最適合入門的物件導向程式設計教程:06 類和物件的Python實現-自定義類的資料封裝

全網最適合入門的物件導向程式設計教程:07 類和物件的Python實現-型別註解

全網最適合入門的物件導向程式設計教程:08 類和物件的Python實現-@property裝飾器

全網最適合入門的物件導向程式設計教程:09 類和物件的Python實現-類之間的關係

全網最適合入門的物件導向程式設計教程:10 類和物件的Python實現-類的繼承和里氏替換原則

全網最適合入門的物件導向程式設計教程:11 類和物件的Python實現-子類呼叫父類方法

全網最適合入門的物件導向程式設計教程:12 類和物件的Python實現-Python使用logging模組輸出程式執行日誌

全網最適合入門的物件導向程式設計教程:13 類和物件的Python實現-視覺化閱讀程式碼神器Sourcetrail的安裝使用

全網最適合入門的物件導向程式設計教程:全網最適合入門的物件導向程式設計教程:14 類和物件的Python實現-類的靜態方法和類方法

全網最適合入門的物件導向程式設計教程:15 類和物件的 Python 實現-__slots__魔法方法

全網最適合入門的物件導向程式設計教程:16 類和物件的Python實現-多型、方法重寫與開閉原則

全網最適合入門的物件導向程式設計教程:17 類和物件的Python實現-鴨子型別與“file-like object“

全網最適合入門的物件導向程式設計教程:18 類和物件的Python實現-多重繼承與PyQtGraph串列埠資料繪製曲線圖

全網最適合入門的物件導向程式設計教程:19 類和物件的 Python 實現-使用 PyCharm 自動生成檔案註釋和函式註釋

全網最適合入門的物件導向程式設計教程:20 類和物件的Python實現-組合關係的實現與CSV檔案儲存

全網最適合入門的物件導向程式設計教程:21 類和物件的Python實現-多檔案的組織:模組module和包package

全網最適合入門的物件導向程式設計教程:22 類和物件的Python實現-異常和語法錯誤

全網最適合入門的物件導向程式設計教程:23 類和物件的Python實現-丟擲異常

全網最適合入門的物件導向程式設計教程:24 類和物件的Python實現-異常的捕獲與處理

全網最適合入門的物件導向程式設計教程:25 類和物件的Python實現-Python判斷輸入資料型別

全網最適合入門的物件導向程式設計教程:26 類和物件的Python實現-上下文管理器和with語句

全網最適合入門的物件導向程式設計教程:27 類和物件的Python實現-Python中異常層級與自定義異常類的實現

全網最適合入門的物件導向程式設計教程:28 類和物件的Python實現-Python程式設計原則、哲學和規範大彙總

全網最適合入門的物件導向程式設計教程:29 類和物件的Python實現-斷言與防禦性程式設計和help函式的使用

全網最適合入門的物件導向程式設計教程:30 Python的內建資料型別-object根類

全網最適合入門的物件導向程式設計教程:31 Python的內建資料型別-物件Object和型別Type

全網最適合入門的物件導向程式設計教程:32 Python的內建資料型別-類Class和例項Instance

全網最適合入門的物件導向程式設計教程:33 Python的內建資料型別-物件Object和型別Type的關係

全網最適合入門的物件導向程式設計教程:34 Python的內建資料型別-Python常用複合資料型別:元組和命名元組

全網最適合入門的物件導向程式設計教程:35 Python的內建資料型別-文件字串和__doc__屬性

全網最適合入門的物件導向程式設計教程:36 Python的內建資料型別-字典

全網最適合入門的物件導向程式設計教程:37 Python常用複合資料型別-列表和列表推導式

全網最適合入門的物件導向程式設計教程:38 Python常用複合資料型別-使用列表實現堆疊、佇列和雙端佇列

全網最適合入門的物件導向程式設計教程:39 Python常用複合資料型別-集合

全網最適合入門的物件導向程式設計教程:40 Python常用複合資料型別-列舉和enum模組的使用

更多精彩內容可看:

給你的 Python 加加速:一文速通 Python 平行計算

一文搞懂 CM3 微控制器除錯原理

肝了半個月,嵌入式技術棧大彙總出爐

電子計算機類比賽的“武林秘籍”

一個MicroPython的開源專案集錦:awesome-micropython,包含各個方面的Micropython工具庫

Avnet ZUBoard 1CG開發板—深度學習新選擇

SenseCraft 部署模型到Grove Vision AI V2影像處理模組

文件和程式碼獲取:

可訪問如下連結進行對文件下載:

https://github.com/leezisheng/Doc

image

本文件主要介紹如何使用 Python 進行物件導向程式設計,需要讀者對 Python 語法和微控制器開發具有基本瞭解。相比其他講解 Python 物件導向程式設計的部落格或書籍而言,本文件更加詳細、側重於嵌入式上位機應用,以上位機和下位機的常見串列埠資料收發、資料處理、動態圖繪製等為應用例項,同時使用 Sourcetrail 程式碼軟體對程式碼進行視覺化閱讀便於讀者理解。

相關示例程式碼獲取連結如下:https://github.com/leezisheng/Python-OOP-Demo

正文

佇列作為一種特殊的資料結構,與集合一樣,完全可以透過列表來實現其功能。佇列是一種特殊的線性表,其特殊性在於僅允許在前端進行刪除操作,而在後端進行插入操作,這與棧的行為模式相似,是一種受限制的線性表。插入操作的端被稱作隊尾,刪除操作的端被稱作隊頭,其核心概念是“先進先出”。

在 Python 中 Queue 模組提供了一個同步的執行緒安全的佇列類,它包括常見的 FIFO(先入先出)、LIFO(後入先出)、PriorityQueue(按優先順序佇列)以及先入先出型別的簡單佇列(SimpleQueue)。

image

佇列的基本操作方法如下:

方法 描述
Q.qsize() 返回佇列的大小。
Q.empty() 如果佇列為空,返回 True,反之 False。
Q.full() 如果佇列滿了,返回 True,反之 False。
Q.get([block[, timeout]]) 獲取佇列,timeout 等待時間。
Q.get_nowait() 相當於 Queue.get(False),非阻塞方法。
Q.put(item) 寫入佇列,timeout 等待時間。
Q.task_done() task_done()呼叫告訴佇列該任務已經處理完畢
Q.join() 實際上意味著等到佇列為空,再執行別的操作

FIFO 佇列

FIFO,即 First In First Out,是我們對佇列最常見的理解定義。想象一下,在銀行或取款機前排成一隊的情況,排在最前面的人通常會最先接受服務,而排在後面的人依次接受服務。Python 的 Queue 類正是如此。

Queue 類通常被用作某種溝通媒介,當一些物件產生資料而其他物件需要消耗這些資料時,且可能以不同的速度進行。設想一個訊息應用從網路接收訊息,但同一時間只能向使用者展示一條訊息。其他的訊息會按照接收順序快取在佇列中。

當考慮下一個物件被消耗時才需要訪問資料結構中的資料時,Queue 類便成為了一個理想的選擇。在這種情況下,使用列表會效率低下,因為在列表的頭部插入(或移除)資料需要移動剩餘的所有元素。

import queue
# 如果不設定長度,預設為無限長
q = queue.Queue(5)
# 注意沒有括號
print(q.maxsize)
q.put(123)
q.put(456)
q.put(789)
q.put(100)
print(q.get())
print(q.get())
print(q.get())

執行結果如下:
image

LIFO 佇列

LIFO 代表“後進先出”(Last In First Out),LIFO 佇列通常被稱為棧。想我們可以將一摞紙作為形象的比喻,你每次只能拿到最上面的一張。你可以往上面放另外一張紙,使它成為最上層,或者可以拿走最上層的紙露出下面的一張。

按照慣例,對棧進行的操作被稱為 push 和 pop,但是 Python 的 queue 模組使用的是與 FIFO 佇列完全相同的 API:put()和 get()。只不過在 LIFO 佇列中,這些方法作用於棧“頂”而不是隊伍的前後端。(實際上,LIFO 和 FIFO 佇列繼承於同一個父類,在相同的方法中實現了不同的操作,這也是我們前面講過的物件導向程式設計中多型的一個非常好的例子。)

LIFO 佇列在底層的實現就是一個標準的列表,相比於列表,Lifo 佇列支援多執行緒併發訪問,同時 Lifo 佇列強制使用棧介面。你無法隨意地向 Lifo 佇列中的錯誤位置插入值。

from queue import LifoQueue

_# 建立物件_
lifoQueue = LifoQueue()
lifoQueue.put(1)
lifoQueue.put(2)
lifoQueue.put(3)
print(lifoQueue.queue)

_# 返回並刪除佇列尾部元素_
print(lifoQueue.get())

print(lifoQueue.queue)

執行結果如下:

image

優先順序佇列

優先順序佇列強制使用一種與之前的佇列實現迥然不同的排序風格。它們仍然使用相同的 get()和 put()函式,不過不同的是,它們不是按照加入順序來返回的,而是首先返回“最重要”的元素。按照約定,最重要或者說優先順序最高的元素是透過小於操作排在最低位的元素。

通常是將一個元組儲存到優先順序佇列中,其中元組的第一個元素代表其優先順序,第二個元素是資料;另一種常用正規化是實現__lt__方法(佇列中多個元素擁有相同的優先順序時無法保證哪個會最先返回)。

優先順序佇列在許多實際應用中非常有用,例如任務排程、網路流量控制和機器學習中的一些演算法。以任務排程為例,可以使用優先順序佇列來管理一組任務。每個任務都有一個優先順序值,優先順序值最高的任務最先被執行。透過使用優先順序佇列,可以確保高優先順序的任務能夠及時得到處理。

如果佇列是空的,get()方法將會阻塞(預設情況下);如果佇列非空,就不會阻塞,也不會等待優先順序更高的元素新增進來。佇列對於還未新增的元素(甚至是已經被提取出來的元素)毫不關心,僅僅基於佇列當前內容做出決定。

import queue

_# 建立優先順序佇列_
q = queue.PriorityQueue()
_# put一個元組,元組格式為(優先順序,資料)_
_# 數字越小,優先順序越高_
q.put((4,'aaaaa'))
q.put((3,'bbbbb'))
q.put((2,'ccccc'))
q.put((1,'ddddd'))
print(q.get())
print(q.get())
print(q.get())
print(q.get())

如下為執行結果:

image

優先順序佇列的通用實現都是基於 heap(堆)資料結構的。Python 實現利用了 heapq 模組來高效地將 heap 儲存在一個尋常的列表中。關於 heap 堆的詳細介紹在後文中會提到。

雙端佇列

雙端佇列 Double-ended queue,簡稱為 Deque,和佇列的操作方式出列同名但不是一個意思。 在電腦科學中,雙端佇列(縮寫為 deque) 是一種抽象資料型別,它概括了一個佇列,其中的元素可以從前(頭) 或後(尾) 新增或刪除。因此也經常被稱為首尾連結串列。

image

雙端佇列有兩種:

(1)輸入限制型雙端佇列:這種佇列中輸入被限制在一端,而刪除則可以兩端同時進行;

(2)輸出限制型雙端佇列:這種佇列只能在一端進行刪除,而插入元素則可以兩端同時進行。
image

雙端佇列在許多實際應用中有著廣泛的用途,其中一些包括:

  • **頁面快取: **瀏覽器的前進、後退功能可以透過雙端佇列實現;
  • 排程演算法: 在某些排程演算法中,雙端佇列可以用於任務的優先順序排程;
  • **優先順序佇列: **雙端佇列可以用作優先順序佇列的基礎資料結構。

以下是關於雙端佇列相關操作的示例程式碼,首先我們需要匯入 collections 模組,這個模組實現了一些專門化的容器,提供了對 Python 的通用內建容器 dict、list、set 和 tuple 的補充:

from collections import deque

_# 建立一個雙端佇列_
deque_obj = deque()

_# 在頭部插入元素_
deque_obj.appendleft(10)
deque_obj.appendleft(15)
deque_obj.appendleft(20)

_# 在尾部插入元素_
deque_obj.append(25)
deque_obj.append(30)
print(deque_obj)

_# 迴圈右移2次_
deque_obj.rotate(2)
print(deque_obj)

_# 從頭部刪除元素_
front = deque_obj.popleft()
print(front)

_#從尾部刪除元素_
rear = deque_obj.pop()
print(rear)

print(deque_obj)

以下為執行結果:

image

環形佇列

在一個普通佇列裡,一旦一個佇列滿了,我們就不能插入下一個元素,即使在佇列前面仍有空間。這時,我們可以利用迴圈佇列實現利用這個佇列之前用過的空間,透過迴圈佇列我們能使用這些空間去儲存新的值。迴圈佇列是一種線性資料結構,其操作表現基於 FIFO(先進先出)原則並且隊尾被連線在隊首之後以形成一個迴圈。它也被稱為“環形緩衝器”。

image

迴圈佇列在邏輯上可以將其視為一個環,當隊首指標等於佇列長度減去一個單位後,再前進一個位置就自動到 0,即取餘運算。我們可以使用 Python 的 collections 模組中的 deque 來實現一個迴圈佇列,在建立 deque 時,可以指定最大長度 collections.deque(maxlen=x) ,如果 maxlen 沒有指定或者是 None,deques 可以增長到任意長度。否則,deque 就限定到指定最大長度,當 deque 滿了,有新項加入時,同樣數量的項就從另一端彈出。示例程式碼如下:

from collections import deque

# 建立一個大小為 3 的迴圈佇列
queue = deque(maxlen=3)

# 新增元素
queue.append('a')
queue.append('b')
queue.append('c')

# 新增第四個元素會導致第一個元素被彈出
queue.append('d')
print(queue)

如下為執行結果:

image

image

相關文章