1. 程式人生 > 其它 >棧和佇列(python實現)

棧和佇列(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個元素,這是為了方便判斷隊空與隊滿。迴圈佇列有如下規定:
    • 若隊首指標與隊尾指標相同,則表示隊空。
    • 若隊頭指標在隊尾指標的下一個位置,則表示隊滿。