棧和佇列(python實現)
棧和佇列
一、棧
1.1 定義
棧式限制在一端進行插入和刪除操作的線性表,具有先進後出的特性,如圖所示:
1.2 基本概念
- 判斷棧是否為空:$ node $ 為棧的頭結點,若 \(node\) 為空,返回 \(True\) , 否則返回 \(False\)
- 入棧:入棧即為在棧的頂部插入元素
- 出棧: 出棧即為刪除頂部的元素
- 取出棧頂元素: 即為獲取棧頂的元素
1.3 順序棧
1、初始化
順序棧在初始化時要定義棧的大小。設定 max 為棧的儲存元素個數
- 棧為空時, self.top 的數值為 -1 ;
- 棧為滿時, self.top = max - 1
程式碼如下:
class SeqStack(object):
def __init__(self, max):
# 順序棧的最大容量
self.max =max
# 當棧為空時, 棧定指標指向 -1
self.top = -1
# 儲存棧元素的陣列
self.stack = [None for i in range(self.max)]
2. 判斷棧是否為空
如果 self.top 的數值為 -1 ,則表示空戰, 返回 True,否則返回 False,程式碼如下:
def empty(self): return self.top is -1
3. 入棧
- 棧滿 : 丟擲異常
- 棧不滿: 將 top 值加一,將新的值放進去
程式碼如下:
def push(self, val):
if self.top == self.max - 1:
raise IndexError('棧已滿')
else:
# 將棧頂指標加一
self.top += 1
self.stack[self.top] = val
4. 出棧
- 棧空:丟擲異常
- 棧不空:將self.top 的數值減 1
程式碼如下:
def pop(self): if self.empty(): raise IndexError('棧空') else: # 此時top指向棧頂元素,存下棧頂元素的值,再將指標下移一個位置 cur = self.stack[self.top] sef.top -= 1 return cur
5. 取出棧頂元素
- 棧空:丟擲異常
- 棧不空:返回棧頂元素
程式碼如下:
def peek(self):
if self.empty():
raise IndexError('棧空!')
else:
return self.stack[self.top]
1.3 鏈棧
鏈棧為棧的鏈式儲存結構,是運算受限的單鏈表,其插入和刪除操作只能在表頭位置進行。設鏈棧頭指標為top,初始化top=None。
鏈棧節點結構與單鏈表節點結構相同,如圖所示:
1. 初始化
- 棧節點初始化:
class Node(object):
def __init__(self, val):
# 節點的資料域
self.val = val
# 節點的指標域
self.next = None
- 棧初始化
class LinkedStack(object):
def __init__(self):
self.top = None
2. 判斷棧是否為空
def empty(self):
return self.top is None
3. 入棧
將待插入節點的 next 指標指向棧頂指標所指向的節點,將棧頂指標指向新節點,程式碼如下:
def push(self, val):
"""
:param val: 入棧元素
"""
newNode = Node(val)
# 新節點的直接後繼指向棧頂指標
newNode.next = self.top
# 將棧頂指標指向新節點
self.top = newNode
4. 出棧
- 棧空:丟擲異常
- 棧不空:將棧頂元素出棧,將棧頂指標指向當前棧頂元素的下一個節點
程式碼如下:
def pop(self):
"""
:return: 返回棧頂元素
"""
# 如果棧為空,則丟擲異常
if self.empty():
raise IndexError('棧為空')
else:
# temp用來儲存棧頂元素
temp = self.top
# 指標指向棧頂的下一個元素
setf.top = self.top.next
return temp
5. 取出棧頂元素
def peek(self):
"""
:return : 返回棧頂元素
"""
if self.empty():
raise IndexError('棧為空')
else:
return self.top.val
二、佇列
2.1 定義
佇列只允許在一端進行插入,在另一端進行刪除,具有先進先出的特性。、
2.2 基本概念
- 隊首: 允許刪除的一端稱為隊首
- 隊尾: 允許插入的一端稱為隊尾
- 入隊: 插入元素的過程稱為入隊
- 出隊: 刪除袁術的過程稱為出隊
- 空佇列: 當佇列中沒有元素時稱為空佇列
2.3 順序佇列
在順序佇列中,將元素入隊時,只需要在隊尾新增一個元素即可。但是,如果將元素出隊,則除了將元素從隊首刪除之外,還需要將其他元素向前移動一個位置。
1. 基本概念
佇列初始化時,規定了佇列的最大元素容量為6。\(self.front、self.rear\)均指向下標為0的位置,如圖所示:
##### 2. 順序佇列初始化class SeqQueue(object):
def __init__(self, max):
self.max = max
self.front = 0
self.rear = 0
self.data = [None for i in range(self.max)]
3. 插入元素
現在向佇列中新增3個元素,分別是5、3、7,新增元素後的佇列如圖所示:
每次插入都是在隊尾插入的,也就是rear所在的位置。每次插入完成後rear自增1。現將佇列中的所有元素出隊,如圖所示:
4. 假溢位
將佇列中所有元素出隊後,佇列的起始位置已經由原本下標為0的位置移動到下標為3的位置了。那麼,如果繼續按這樣入隊、出隊的形式,是不是到最後會存在這樣一種現象—佇列中是空的,沒有任何元素,卻無法繼續將元素進隊?結果是顯而易見的,最後就是會出現這種現象。這種現象叫作假溢位。
5. 迴圈佇列
為了解決假溢位現象,引入了迴圈佇列的概念。所謂迴圈佇列,就是將佇列首尾連線起來。當rear的數值超過陣列的最大長度時,先判斷front是否在下標為0的位置,如果不在,則將rear指向下標0。
為方便理解,將迴圈隊列表示為一個環形的圖,起始時, rear 和 front 指向下標 0 ,如圖所示:
現在向佇列中新增5個元素,分別是5、3、7、8、4。在迴圈佇列中,新增元素也是在隊尾新增的,即rear下標處,如圖所示:
從迴圈佇列中刪除兩個元素,刪除元素是在隊首刪除的,如圖所示:
此時,在順序佇列中,當front等於rear時表示隊空。但是在迴圈佇列中,很難區分front等於rear時是空隊還是滿隊。這裡的解決方法是保留一個元素空間,當佇列滿時,還有一個空的位置。由於rear有可能比front大,也有可能比front小,所以儘管相差一個位置,其實有可能是相差一整圈。
若佇列的最大容量為max,則隊滿的條件是(rear+1)%max==front
6. 迴圈佇列初始化
self.front 指向隊首下標,self.rear 指向隊尾下標,max 表示佇列的最大容量。迴圈佇列初始化程式碼實現如下:
class CircleQueue(object):
def __init__(self, max):
# 佇列最大容量
self.max = max
# 儲存佇列元素的陣列
self.data = [None for i in range(self.max)]
# 隊首指標
self.front = 0
# 隊尾指標
self.rear = 0
7. 迴圈佇列的基本操作
- 判斷隊空
def empty(self):
return self.front == self.rear
-
入隊
- 隊滿:丟擲異常
- 隊不滿:插入元素,並且self.rear 自增 1,若self + 1 大於 max,則將 self.rear 指向下標0 。
def push(self, val):
"""
:param val:待插入的關鍵字
"""
if (self.rear + 1) % self.max == self.front:
raise IndexError('隊滿')
# 在隊尾插入新的關鍵字
self.data[self.rear] = val
# 修改隊尾指標
self.rerr = (self.rear + 1) % self.max
-
出隊
- 隊空:丟擲異常
- 隊不空:獲取隊首元素,隊首指標加一,返回隊首元素
程式碼如下:
def pop(self):
if self.empty():
raise IndexError('隊空')
# 佇列不為空,獲取隊首元素
cur = self.data[self.front]
# 修改隊首指標,指向下一個位置
self.front = (self.front + 1) % self.max
# 返回原隊首元素
return cur
-
獲取隊首元素
- 隊空:丟擲異常
- 隊不空: 返回隊首元素
程式碼如下:
def peek(self):
if self.empty():
raise IndexError('隊空')
return self.data[self.front]
2.3 鏈式佇列
順序佇列在插入和刪除時需要移動大量的元素,因此引入了鏈式佇列。鏈式佇列插入和刪除操作方便,不需要移動元素,只需要改變指標的指向即可。
鏈式佇列節點結構與單鏈表的節點結構一樣,佇列節點包括一個數據域data和一個指標域 next。節點結構如圖所示:
1. 初始化
- 節點類Node初始化
class Node(object):
def __init__(self, data):
self.data = data
self.next = None
- 佇列初始化: 對於不帶頭節點的鏈式佇列,初始化時,鏈式佇列的front指標指向None。
class LinkedQueue(object):
def __init__(self):
self.front = None
2. 判斷隊空
def empty(self):
return self.front is None
3. 入隊
- 隊空:將隊首指標指向待插入的新節點
- 隊不空:則遍歷到隊尾,將新節點插入到隊尾
程式碼如下:
def push(self, val):
new = Node(val)
if self.front == None:
raise IndexError('隊空!')
else:
cur = self.front
# 遍歷佇列
while cur.next != None:
cur = cur.next
# 在隊尾插入元素
cur.next = new
4. 出隊
判斷佇列是否為空,若佇列為空,則丟擲異常,否則刪除隊首節點,程式碼實現如下
def pop(self):
if self.empty():
raise IndexError('隊空')
else:
cur = self.front
# 將隊首指標指向隊首節點的後繼節點
self.front = self.front.next
return cur
5. 獲取隊首元素
判斷佇列是否為空,若佇列為空,則丟擲異常;若佇列不為空,則返回隊首元素,程式碼實現如下
def peek(self):
if self.empty():
raise IndexError('隊空')
els:
return self.front.data
總結
- 棧是一種限制只能在棧頂進行插入和刪除操作的線性表,具有先進後出的特性,各種運算的時間複雜度均為O(1)。
- 佇列是一種只允許在隊尾進行插入、只允許在隊首進行刪除的線性表,具有先進先出的特性。其插入和刪除的時間複雜度均為O(1)。
- 迴圈佇列就是首尾連線起來的佇列。假設儲存佇列的空間大小為n,則只能存n-1個元素,這是為了方便判斷隊空與隊滿。迴圈佇列有如下規定:
- 若隊首指標與隊尾指標相同,則表示隊空。
- 若隊頭指標在隊尾指標的下一個位置,則表示隊滿。