🧱用Python 與 串列 來實現 Stack(堆疊)

更新於 2024/08/27閱讀時間約 1 分鐘

在之前的教學中,已經學會了Node和Linked List的實作,
用Python實現了單向鏈結串列Singly linked list雙向鏈結串列Doubly linked list

今天要承接之前打下的基礎,用雙向鏈結串列來實作Stack 堆疊。


定義

是一個線性長條狀的資料結構。

規定推入資料取出資料都必須在頂端TOP操作。

(就像餐廳疊盤子,或者,書桌上疊書本的那種感覺。)

也由於這項規定,Stack先天具有先進後出First-in Last-out的這種特質。

raw-image

支援的操作與介面


stack.push(data)

推入一筆新資料data進入stack的頂端。

raw-image

stack.top()

取得stack頂端目前的資料。

raw-image

stack.pop()

取得stack頂端目前的資料,並且將此資料從stack頂端移除。

raw-image

len( stack )

取得stack的大小,也就是現在有幾筆資料存在stack裡。

raw-image

stack.is_empty()

判斷stack是否為空。

raw-image

優點

1.在頂端新增、取得、移除資料是O(1) 常數時間

2.可以根據需求動態延伸或縮短,有效利用記憶體空間。


缺點

1.不支援random acess,不支援Stack[ index ] 這種操作。

2.如果要取得下層的資料,必須先將頂端的資料移除


Stack的class定義 與 建構子(初始化函式)

class Stack:

  def __init__(self):
# 底層用 雙向鏈結串列來實作 Stack
    self.stack = LinkedList()

Stack常見的function實現


1.在stack頂端插入新元素


直接在stack頂端放入新元素,對應的底層實作就是在尾巴串上新結點。


時間複雜度: O(1) 常數時間

  def push(self, data):

    self.stack.insertAtTail(data)
    return

2.讀取stack頂端元素


直接返回stack頂端的元素,對應的底層實作就是返回尾巴結點的資料。

實作上有個細節需要留意,假如stack是空的,直接返回None即可。


時間複雜度: O(1) 常數時間

  def top(self):

    if self.stack.tail is None:
      return None

    return self.stack.tail.data

3.從stack頂端取出舊元素


直接返回並且移除stack頂端的元素,
對應的底層實作就是在返回並且移除尾巴結點的資料。


時間複雜度: O(1) 常數時間

  def pop(self):
    return self.stack.removeAtTail()

4.取得stack的長度


返回stack的長度,也代表stack內部資料的總數量。

對應的底層實作就是在返回串列的長度。


時間複雜度: O(1) 常數時間

  def __len__(self):
    return len(self.stack)

5.檢查stack是否為空


如果stack 的長度為0,代表stack是空的,內部沒有任何資料。


時間複雜度: O(1) 常數時間

  def is_empty(self):
    return 0 == self.stack.size

完整的Stack實作和程式碼,
底層是Doubly Linked List雙向鏈結串列。


class Node:
def __init__(self, data):
# 儲存節點的資料
self.data = data

# 指向下一個節點,初始化為空 None
self.next = None

# 指向前一個節點,初始化為空 None
self.prev = None


class LinkedList:
def __init__(self):

# 頭部節點初始化為空
self.head = None
# 尾巴節點初始化為空
self.tail = None

# 串列長度初始化為0
self.size = 0


def insertAtHead(self,data):
new_node = Node(data)

self.size += 1

# If the list is empty
if self.tail is None:
self.head = new_node
self.tail = new_node
else:

# Update linkage
new_node.next = self.head
self.head.prev = new_node

self.head = new_node
return


def insertAtTail(self, data):
new_node = Node(data)

self.size += 1

# If the list is empty
if self.head is None:
self.head = new_node
self.tail = new_node
else:

# Update linkage
self.tail.next = new_node
new_node.prev = self.tail

self.tail = new_node

return


def removeAtHead(self):
if self.head is None:
return
else:
self.size -= 1
oldHead = self.head

# Update linkage
if self.head == self.tail:
self.tail = None
self.head = None
else:
self.head = self.head.next

# Break linkage
oldHead.next = None

if self.head:
self.head.prev = None

return oldHead


def removeAtTail(self):
if self.tail is None:
return
else:
self.size -= 1
oldTail = self.tail

# Update linkage
if self.tail == self.head:
self.tail = None
self.head = None
else:
self.tail = self.tail.prev

# Break linkage
oldTail.prev = None

if self.tail:
self.tail.next = None

return oldTail


def traverse(self):

print("Forward traversal")
cur = self.head

while cur:
print(cur.data, end=' <-> ')
cur = cur.next
print("None")

print(f"Size of singly linked list: {self.size} \n", )
return


def backward_traverse(self):

print("Backward traversal")
cur = self.tail

while cur:
print(cur.data, end=' <-> ')
cur = cur.prev
print("None")

print(f"Size of singly linked list: {self.size} \n", )
return


def search(self, target):
cur = self.head
while cur:
if cur.data == target:
return cur

cur = cur.next

return None


def removeAfter(self, node):

self.size -= 1

removed = node.next
the_one_after_removal = node.next.next if node.next else None

# Update linkage
if the_one_after_removal:
the_one_after_removal.prev = node

node.next = the_one_after_removal
# Break linkage
if removed:
removed.next = None
removed.prev = None

return removed


def insertAfter(self, node, new_node):

self.size += 1

the_one_after_insertion = node.next

# Update linkage
node.next = new_node
new_node.prev = node

the_one_after_insertion.prev = new_node
new_node.next = the_one_after_insertion
return


def __contains__(self, item):

cur = self.head
while cur:
if cur.data == item:
return True

cur = cur.next

return False


def __len__(self):
return self.size


class Stack:
def __init__(self):
self.stack = LinkedList()

def push(self, data):
self.stack.insertAtTail(data)
return

def pop(self):
return self.stack.removeAtTail()

def top(self):
if self.stack.tail is None:
return None

return self.stack.tail.data

def is_empty(self):
return 0 == self.stack.size

def __len__(self):
return len(self.stack)

def __contains__(self, item):
return item in self.stack

def traverse(self):
self.stack.traverse()
return


def test():
print(f"Empty stack\n")
stack = Stack()

for i in range(1, 5):
print(f"Push {i}...")
stack.push(i)

print()
print(f"Is stack empty? {stack.is_empty()}")
print(f"Size of stack: { len(stack) } ")
print()

for i in range(1,6):
print(f"Is {i} in stack? = { i in stack}")

print()
stack.traverse()

print(f"Top of stack = {stack.top()}")
stack.pop()

print(f"Top of stack = {stack.top()}")
stack.pop()

print(f"Top of stack = {stack.top()}")
stack.pop()

print(f"Top of stack = {stack.top()}")
stack.pop()

print(f"Is stack empty? {stack.is_empty()}")

return

if __name__ == "__main__":
test()

測試輸出

Empty stack

Push 1...
Push 2...
Push 3...
Push 4...

Is stack empty? False
Size of stack: 4

Is 1 in stack? = True
Is 2 in stack? = True
Is 3 in stack? = True
Is 4 in stack? = True
Is 5 in stack? = False

Forward traversal
1 <-> 2 <-> 3 <-> 4 <-> None
Size of singly linked list: 4

Top of stack = 4
Top of stack = 3
Top of stack = 2
Top of stack = 1
Is stack empty? True

結語


這篇我們透過Linked List作為底層的輔助資料結構,來實現完整的Stack class 和 interface function。

讀者可以搭配先前的教學文章,一起復習相關的資料結構。


Stack相關的演算法練習題與詳解


Stack堆疊應用題 合法括號配對字串 Leetcode #20_Valid Parentheses


一魚多吃 用stack來解 最長合法括弧字串 Leetcode #32 Longest Valid Parenthese


應用題 用Stack實作Queue_Leetcode #232 Implement Queue using Stacks


一魚多吃 用stack來解 Backspace String Compare_Leetcode #844


一魚多吃 用stack來驗證 合法括弧字串 678. Valid Parenthesis String


用stack 模擬陣列生成 Build Array w/ Stack Operations_Leetcode 1441

avatar-img
90會員
425內容數
由有業界實戰經驗的演算法工程師, 手把手教你建立解題的框架, 一步步寫出高效、清晰易懂的解題答案。 著重在讓讀者啟發思考、理解演算法,熟悉常見的演算法模板。 深入淺出地介紹題目背後所使用的演算法意義,融會貫通演算法與資料結構的應用。 在幾個經典的題目融入一道題目的多種解法,或者同一招解不同的題目,擴展廣度,並加深印象。
留言0
查看全部
avatar-img
發表第一個留言支持創作者!
在資料結構與演算法裡, 最簡單的線性資料結構除了array之外就是linked list鏈結串列了。 Linked list又有分為單向Singly linked list 和雙向Doubly linked list
在這次的BMI(身體質量指標)計算教學裡, 將學會如何用python來接收使用者輸入的訊息,並且做一些簡單的四則運算。
從範例學python的目標讀者: 針對剛進入的初學者,想學習Python語言。 有基礎本數學邏輯基礎即可。 從小遊戲學python的目標讀者: 針對已經有經驗的C/C++, Python, 或其他有程式基礎的讀者。 想實作一些小專案,從實做中學習如何分析需求、元件分拆、到底層實作
在程式語言裡,最基本的第一堂課通常就是最簡單也最直接的輸入和輸出, 今天,會從大家耳熟能詳的"Hello Wolrd"這個經典範例開始介紹 Python的基本的輸出print語法,最後以冷笑話的範例作為結尾的Demo。 從電腦的視角來看,什麼叫做Output輸出?
在資料結構與演算法裡, 最簡單的線性資料結構除了list之外就是linked list鏈結串列了。 Linked list又有分為單向Singly linked list 和雙向Doubly linked list 在這篇文章,會從最基礎的Singly linked list開始講起。 定義
前言 相傳有一個故事, 數學家高斯的小學數學老師出了一道從1+2+3+...+100的習題, 想讓活潑好動的小學生們算一整節課,消耗一下多餘的體力, 結果老師剛說完題目沒過多久,小高斯就算出了答案。 原來,他發現數列兩端可以兩兩配對:1+100,2+99……每一對的和都是101,共有50對,所
在資料結構與演算法裡, 最簡單的線性資料結構除了array之外就是linked list鏈結串列了。 Linked list又有分為單向Singly linked list 和雙向Doubly linked list
在這次的BMI(身體質量指標)計算教學裡, 將學會如何用python來接收使用者輸入的訊息,並且做一些簡單的四則運算。
從範例學python的目標讀者: 針對剛進入的初學者,想學習Python語言。 有基礎本數學邏輯基礎即可。 從小遊戲學python的目標讀者: 針對已經有經驗的C/C++, Python, 或其他有程式基礎的讀者。 想實作一些小專案,從實做中學習如何分析需求、元件分拆、到底層實作
在程式語言裡,最基本的第一堂課通常就是最簡單也最直接的輸入和輸出, 今天,會從大家耳熟能詳的"Hello Wolrd"這個經典範例開始介紹 Python的基本的輸出print語法,最後以冷笑話的範例作為結尾的Demo。 從電腦的視角來看,什麼叫做Output輸出?
在資料結構與演算法裡, 最簡單的線性資料結構除了list之外就是linked list鏈結串列了。 Linked list又有分為單向Singly linked list 和雙向Doubly linked list 在這篇文章,會從最基礎的Singly linked list開始講起。 定義
前言 相傳有一個故事, 數學家高斯的小學數學老師出了一道從1+2+3+...+100的習題, 想讓活潑好動的小學生們算一整節課,消耗一下多餘的體力, 結果老師剛說完題目沒過多久,小高斯就算出了答案。 原來,他發現數列兩端可以兩兩配對:1+100,2+99……每一對的和都是101,共有50對,所
你可能也想看
Google News 追蹤
Thumbnail
*合作聲明與警語: 本文係由國泰世華銀行邀稿。 證券服務係由國泰世華銀行辦理共同行銷證券經紀開戶業務,定期定額(股)服務由國泰綜合證券提供。   剛出社會的時候,很常在各種 Podcast 或 YouTube 甚至是在朋友間聊天,都會聽到各種市場動態、理財話題,像是:聯準會降息或是近期哪些科
情感分析是一種自然語言處理技術,用於自動識別和分析文本中的情感傾向,通常是正向、負向或中性。 我們可以使用 NLTK 來實現一個基於單純貝斯分類器的情感分析模型。
Thumbnail
近年來,隨著人工智慧技術的快速發展,Python結合生成式AI正逐漸成為行銷領域的重要利器。對於行銷專業人士來說,這股趨勢更是值得關注和深入研究。 Python和AI將會為行銷領域帶來什麼改變?
Thumbnail
本篇內容介紹如何使用 moviepy library 將影片檔轉為mp3音樂檔。
Thumbnail
如何匯入Excel或CSV檔案? 如何更改欄位名稱? 如何從舊欄位中組合新欄位? 如何擷取舊欄位內容成新欄位? 如何篩選資料?
Thumbnail
前言 在上一篇文章中,分享了第一次使用 IBM Watsonx 的經歷,以及我對 Prompt lab 功能的初步探索。繼續這個話題,本文將探討 Watsonx 平台對 Python SDK 的支持,以及實作幾個 LLM 的應用,這一特性為開發者提供了極大的便利,使得在此平台上進行開發和應用大型語
Thumbnail
根據泰勒定理,f(x)可以寫成右邊一長串的導數的組合 為了更好理解這個東西我們可以用python實作 首先定義f(x)和定義factorial怎麼算 然後寫泰勒定理 f(x) = f(a) + f'(a)(x-a) ....後面還有一串 注意公式往後面看其實是有規律的 例如從
在這篇文章中,我們將講解一些常見的語音技術以及如何在Python中使用這些技術。 安裝套件 匯入套件 語音辨識:
Thumbnail
*合作聲明與警語: 本文係由國泰世華銀行邀稿。 證券服務係由國泰世華銀行辦理共同行銷證券經紀開戶業務,定期定額(股)服務由國泰綜合證券提供。   剛出社會的時候,很常在各種 Podcast 或 YouTube 甚至是在朋友間聊天,都會聽到各種市場動態、理財話題,像是:聯準會降息或是近期哪些科
情感分析是一種自然語言處理技術,用於自動識別和分析文本中的情感傾向,通常是正向、負向或中性。 我們可以使用 NLTK 來實現一個基於單純貝斯分類器的情感分析模型。
Thumbnail
近年來,隨著人工智慧技術的快速發展,Python結合生成式AI正逐漸成為行銷領域的重要利器。對於行銷專業人士來說,這股趨勢更是值得關注和深入研究。 Python和AI將會為行銷領域帶來什麼改變?
Thumbnail
本篇內容介紹如何使用 moviepy library 將影片檔轉為mp3音樂檔。
Thumbnail
如何匯入Excel或CSV檔案? 如何更改欄位名稱? 如何從舊欄位中組合新欄位? 如何擷取舊欄位內容成新欄位? 如何篩選資料?
Thumbnail
前言 在上一篇文章中,分享了第一次使用 IBM Watsonx 的經歷,以及我對 Prompt lab 功能的初步探索。繼續這個話題,本文將探討 Watsonx 平台對 Python SDK 的支持,以及實作幾個 LLM 的應用,這一特性為開發者提供了極大的便利,使得在此平台上進行開發和應用大型語
Thumbnail
根據泰勒定理,f(x)可以寫成右邊一長串的導數的組合 為了更好理解這個東西我們可以用python實作 首先定義f(x)和定義factorial怎麼算 然後寫泰勒定理 f(x) = f(a) + f'(a)(x-a) ....後面還有一串 注意公式往後面看其實是有規律的 例如從
在這篇文章中,我們將講解一些常見的語音技術以及如何在Python中使用這些技術。 安裝套件 匯入套件 語音辨識: