1. 程式人生 > >一本正經的聊資料結構(3):棧和佇列

一本正經的聊資料結構(3):棧和佇列

![](https://cdn.geekdigging.com/DataStructure/head.png) 前文傳送門: [「一本正經的聊資料結構(1):時間複雜度」](https://www.geekdigging.com/2020/03/28/6072951828/) [「一本正經的聊資料結構(2):陣列與向量」](https://www.geekdigging.com/2020/04/14/3143023839/) ## 引言 前一篇內容我們介紹了陣列和向量,雖然說向量是陣列的一個升級版,但是在另一個維度上,他們都屬於線性結構。 那麼什麼是線性結構呢? 線性結構是一個有序資料元素的集合。常用的線性結構有:線性表,棧,佇列,雙佇列,陣列,串。 線性結構是最常用的資料結構,它最大的特點是資料元素之間存在一對一的線性關係。 線性結構擁有兩種不同的儲存結構,即順序儲存結構和鏈式儲存結構。 順序儲存的線性表稱為順序表,順序表中的儲存元素是連續的。 鏈式儲存的線性表稱為連結串列,連結串列中的儲存元素不一定是連續的,元素節點中存放資料元素以及相鄰元素的地址資訊。 線性結構中存在兩種操作受限的使用場景,就是我們本文要介紹的棧和佇列。 至於為什麼說棧和佇列是受限的線型結構,我們下面細聊。 ## 棧 棧是一種比較奇葩的資料結構,棧的結構是支援物件的插入和刪除操作,但是,棧操作的範圍僅限於棧的某一特定端,就是下面這樣的。 ![](https://cdn.geekdigging.com/DataStructure/zhan.png) 棧遵循先進後出( last-in-first-out, LIFO )的規律,這是重點。 棧一般使用兩種方式來實現: 1. 順序表:採用順序儲存結構可以模擬棧儲存資料的特點,從而實現棧儲存結構。 2. 連結串列:採用鏈式儲存結構實現棧結構。 注意,這兩種實現方式的區別,僅限於資料元素在實際物理空間上存放的相對位置,順序棧底層採用的是陣列,鏈棧底層採用的是連結串列。 棧結構我們還是會經常用到,一個非常經典的場景就是在瀏覽器的後退功能中。 例如我們每次開啟一個頁面,瀏覽器都會把這個頁面放入棧中,當我們點選後退按鈕的時候,在從棧中將這個頁面取出來。 ### 順序棧 順序棧,是用順序表實現棧儲存結構。 棧儲存結構操作資料元素必須遵守 「先進後出 LIFO 」 的原則。 順序表的底層是使用陣列來實現的,簡單理解可以直接理解成陣列。 只是棧結構對資料的存取過程有特殊的限制,而陣列是沒有的。 ### 鏈棧 鏈棧,是用連結串列實現棧儲存結構。 連結串列這個結構在前面沒聊過,簡單畫個圖大家理解下: ![](https://cdn.geekdigging.com/DataStructure/lianzhan.gif) 連結串列的結構相比較陣列而言就稍微有些複雜了,連結串列的每個節點由兩部分組成,一個是存放資料的,叫資料域,另一個是存放指標的,叫指標域。 ![](https://cdn.geekdigging.com/DataStructure/lianbiao.gif) 陣列在記憶體中是連續的,所以我們可以輕鬆的知道陣列的每一個元素的位置,而連結串列在記憶體中是分散的,我們需要一個指標來指明下一個元素在哪裡。 ![](https://cdn.geekdigging.com/DataStructure/lianbiao_1.gif) 這裡介紹的其實是最簡單的一種連結串列,叫單鏈表,顧名思義,除了單鏈表之外還有雙鏈表,這個我們有機會後面再聊。 那麼鏈棧是將連結串列的頭部作為棧頂,尾部作為棧底。 > 將連結串列頭部作為棧頂的一端,可以避免在實現資料 「入棧」 和 「出棧」 操作時做大量遍歷連結串列的耗時操作。 連結串列的頭部作為棧頂,意味著: * 在實現資料"入棧"操作時,需要將資料從連結串列的頭部插入。 * 在實現資料"出棧"操作時,需要刪除連結串列頭部的首元節點。 因此,鏈棧實際上就是一個只能採用頭插法插入或刪除資料的連結串列。 ### Python 實現棧 在 Python 中,棧並不是一個基礎資料結構,不過我們可以通過程式碼來簡單的實現它。 因為棧是可以通過兩種方式來實現,一種是順序表,另一種是連結串列: 首先是最簡單的通過順序表來實現棧,這裡使用的是 Python 中的 list 列表: ```python class Stack(object): def __init__(self): ''' 建立空列表實現棧 ''' self.__list = [] def is_empty(self): ''' 判斷是否為空 :return: ''' return self.__list == [] def push(self,item): ''' 壓棧,新增元素 :param item: :return: ''' self.__list.append(item) def pop(self): ''' 彈出棧,將元素取出 :return: ''' if self.is_empty(): return else: return self.__list.pop() ``` 如果不想使用順序表來實現,還可以使用連結串列,這裡使用的是單鏈表,連結串列的結構需要先提前定義: ```python class Node(object): ''' 節點實現 ''' def __init__(self,elem): self.elem = elem self.next = None class Stack(object): def __init__(self): ''' 初始化連結串列頭 ''' self.__head = None def is_empty(self): return self.__head is None def push(self, item): ''' 壓棧 :param item: :return: ''' node = Node(item) node.next = self.__head self.__head = node def pop(self): ''' 彈出棧 :return: ''' if self.is_empty(): return else: p = self.__head self.__head = p.next return p.elem ``` 在連結串列的實現中,我這裡先定義了連結串列的資料結構,然後才定義了棧。 上面兩段程式碼都非常簡單,只實現了最簡單的兩個功能,入棧和出棧,感興趣的同學可以自己動手實現下。 ## 佇列 與棧一樣,佇列( queue) 也是存放資料物件的一種容器,其中的資料物件也按線性的邏輯次序排列。 佇列和棧不一樣的地方在於棧是先進後出,而佇列是先進先出( first-in-first-out, FIFO )。 ![](https://cdn.geekdigging.com/DataStructure/duilie.png) 同棧一樣的是佇列也有兩種實現方式: * 順序佇列:在順序表的基礎上實現的佇列結構。 * 鏈佇列:在連結串列的基礎上實現的佇列結構。 ### Python 中的 Queue 在 Python 的標準庫中,Python 為我們提供了執行緒安全的佇列 Queue (總算不用我再自己寫個隊列了),使用方法異常簡單: 先進先出佇列 (FIFO) : ```python import queue q1 = queue.Queue(maxsize=5) for i in range(5): q1.put(i) while not q1.empty(): print('q1:',q1.get()) # 結果輸出 q1: 0 q1: 1 q1: 2 q1: 3 q1: 4 ``` Queue 這個標準庫中,還為我們提供了 LIFO 佇列,即先進後出佇列,和我們前面介紹的棧非常類似,翻了下原始碼,看到是使用 list 實現的,和我們上面的實現基本一致,使用方式如下: ```python import queue q2 = queue.LifoQueue(maxsize=5) for i in range(5): q2.put(i) while not q2.empty(): print('q2:',q2.get()) # 結果輸出 q2: 4 q2: 3 q2: 2 q2: 1 q2: 0 ``` 本篇內容就這樣了,涉及到的程式碼肯定會上傳程式碼倉庫,有興趣的同學可以去翻翻看。 ## 示例程式碼 [示例程式碼-Github](https://github.com/meteor1993/python-learning/tree/master/data_structure "示例程式碼-Github") [示例程式碼-Gitee](https://gitee.com/inwsy/python-learning/tree/master/data_structure "示例程式碼-Gitee")