Stack(堆疊)資料結構完整教學

掌握 LIFO 原理與 Python 實作技巧

Stack(堆疊)資料結構完整教學

什麼是 Stack?

Stack(堆疊)是一種遵循 LIFO(Last In First Out,後進先出) 原則的線性資料結構。想像一疊盤子,你只能從最上面拿取或放置盤子,最後放上去的盤子會第一個被拿走。

視覺化概念

現實生活的例子:疊盤子
     ┌─────┐
     │  3  │ ← 最後放入(第一個取出)
     ├─────┤
     │  2  │
     ├─────┤
     │  1  │ ← 最先放入(最後取出)
     └─────┘
       底部

Stack 資料結構:
     Top
      ↓
    [30]  ← Push/Pop 都在這裡進行
    [20]
    [10]
   Bottom

Stack 的基本操作

  1. Push:將元素放入堆疊頂端
  2. Pop:從堆疊頂端移除元素並回傳
  3. Peek/Top:查看堆疊頂端元素(不移除)
  4. IsEmpty:檢查堆疊是否為空
  5. Size:取得堆疊中的元素數量

為什麼需要 Stack?

實際應用場景

# 1. 函式呼叫堆疊
def a():
    print("In function a")
    b()
    print("Back in function a")

def b():
    print("In function b")
    c()
    print("Back in function b")

def c():
    print("In function c")

# 呼叫堆疊: main -> a() -> b() -> c()
# 返回順序: c() -> b() -> a() -> main

# 2. 瀏覽器的上一頁功能
browser_history = Stack()
browser_history.push("google.com")
browser_history.push("youtube.com")
browser_history.push("github.com")
# 按上一頁會回到 youtube.com

# 3. 文字編輯器的復原功能
undo_stack = Stack()
undo_stack.push("輸入 Hello")
undo_stack.push("輸入 World")
# Ctrl+Z 會復原最後的操作

基本實作

1. 使用 Python List 實作(最簡單)

class Stack:
    """使用 Python list 實作的 Stack"""
    
    def __init__(self):
        self.items = []
    
    def push(self, item):
        """將元素加入堆疊頂端 - O(1)"""
        self.items.append(item)
    
    def pop(self):
        """移除並回傳堆疊頂端元素 - O(1)"""
        if self.is_empty():
            raise IndexError("Stack is empty")
        return self.items.pop()
    
    def peek(self):
        """查看堆疊頂端元素 - O(1)"""
        if self.is_empty():
            raise IndexError("Stack is empty")
        return self.items[-1]
    
    def is_empty(self):
        """檢查堆疊是否為空 - O(1)"""
        return len(self.items) == 0
    
    def size(self):
        """回傳堆疊大小 - O(1)"""
        return len(self.items)
    
    def __str__(self):
        """視覺化顯示堆疊"""
        if self.is_empty():
            return "Stack(empty)"
        
        # 從上到下顯示
        result = "Stack (top to bottom):\n"
        for i in range(len(self.items) - 1, -1, -1):
            if i == len(self.items) - 1:
                result += f"  [{self.items[i]}] ← top\n"
            else:
                result += f"  [{self.items[i]}]\n"
        return result.strip()

# 使用範例
stack = Stack()
print(stack)  # Stack(empty)

# Push 操作
for i in [10, 20, 30]:
    stack.push(i)
    print(f"Pushed {i}")

print(stack)
# Stack (top to bottom):
#   [30] ← top
#   [20]
#   [10]

# Pop 操作
print(f"Popped: {stack.pop()}")  # 30
print(f"Peek: {stack.peek()}")    # 20
print(f"Size: {stack.size()}")    # 2

2. 使用 Linked List 實作

class Node:
    """節點類別"""
    def __init__(self, data):
        self.data = data
        self.next = None

class StackWithLinkedList:
    """使用 Linked List 實作的 Stack"""
    
    def __init__(self):
        self.top = None
        self._size = 0
    
    def push(self, item):
        """將元素加入堆疊頂端 - O(1)"""
        new_node = Node(item)
        new_node.next = self.top
        self.top = new_node
        self._size += 1
    
    def pop(self):
        """移除並回傳堆疊頂端元素 - O(1)"""
        if self.is_empty():
            raise IndexError("Stack is empty")
        
        data = self.top.data
        self.top = self.top.next
        self._size -= 1
        return data
    
    def peek(self):
        """查看堆疊頂端元素 - O(1)"""
        if self.is_empty():
            raise IndexError("Stack is empty")
        return self.top.data
    
    def is_empty(self):
        """檢查堆疊是否為空 - O(1)"""
        return self.top is None
    
    def size(self):
        """回傳堆疊大小 - O(1)"""
        return self._size
    
    def __str__(self):
        """視覺化顯示堆疊"""
        if self.is_empty():
            return "Stack(empty)"
        
        result = []
        current = self.top
        while current:
            result.append(str(current.data))
            current = current.next
        
        output = "Stack (top to bottom):\n"
        for i, item in enumerate(result):
            if i == 0:
                output += f"  [{item}] ← top\n"
            else:
                output += f"  [{item}]\n"
        return output.strip()

# 使用範例
stack = StackWithLinkedList()
for i in [1, 2, 3]:
    stack.push(i * 10)

print(stack)
print(f"Pop: {stack.pop()}")
print(f"After pop:\n{stack}")

3. 固定大小的 Stack

class FixedStack:
    """固定大小的 Stack"""
    
    def __init__(self, capacity):
        self.capacity = capacity
        self.items = [None] * capacity
        self.top = -1
    
    def push(self, item):
        """將元素加入堆疊頂端 - O(1)"""
        if self.is_full():
            raise OverflowError("Stack is full")
        
        self.top += 1
        self.items[self.top] = item
    
    def pop(self):
        """移除並回傳堆疊頂端元素 - O(1)"""
        if self.is_empty():
            raise IndexError("Stack is empty")
        
        data = self.items[self.top]
        self.items[self.top] = None  # 清除參考
        self.top -= 1
        return data
    
    def peek(self):
        """查看堆疊頂端元素 - O(1)"""
        if self.is_empty():
            raise IndexError("Stack is empty")
        return self.items[self.top]
    
    def is_empty(self):
        """檢查堆疊是否為空 - O(1)"""
        return self.top == -1
    
    def is_full(self):
        """檢查堆疊是否已滿 - O(1)"""
        return self.top == self.capacity - 1
    
    def size(self):
        """回傳堆疊大小 - O(1)"""
        return self.top + 1
    
    def __str__(self):
        if self.is_empty():
            return "FixedStack(empty)"
        
        result = f"FixedStack(capacity={self.capacity}):\n"
        for i in range(self.top, -1, -1):
            if i == self.top:
                result += f"  [{self.items[i]}] ← top\n"
            else:
                result += f"  [{self.items[i]}]\n"
        return result.strip()

# 使用範例
fixed_stack = FixedStack(5)
print(f"Capacity: {fixed_stack.capacity}")

try:
    for i in range(6):
        fixed_stack.push(i)
        print(f"Pushed {i}, size: {fixed_stack.size()}")
except OverflowError as e:
    print(f"Error: {e}")

print(fixed_stack)

經典應用:括號匹配

def is_balanced_parentheses(expression):
    """檢查括號是否平衡"""
    stack = Stack()
    
    # 定義配對關係
    pairs = {
        ')': '(',
        ']': '[',
        '}': '{'
    }
    
    for char in expression:
        if char in '([{':
            # 左括號,push 進 stack
            stack.push(char)
        elif char in ')]}':
            # 右括號,檢查配對
            if stack.is_empty():
                return False
            
            if stack.pop() != pairs[char]:
                return False
    
    # 最後 stack 應該是空的
    return stack.is_empty()

# 測試
test_cases = [
    ("()", True),
    ("()[]{}", True),
    ("([{}])", True),
    ("([)]", False),
    ("((", False),
    ("))", False),
    ("", True)
]

for expr, expected in test_cases:
    result = is_balanced_parentheses(expr)
    status = "✓" if result == expected else "✗"
    print(f"{status} '{expr}' → {result}")

經典應用:後序表達式求值

def evaluate_postfix(expression):
    """
    計算後序表達式(逆波蘭表示法)
    例如: "2 3 + 5 *" = (2 + 3) * 5 = 25
    """
    stack = Stack()
    
    for token in expression.split():
        if token in '+-*/':
            # 運算符:pop 兩個運算元
            if stack.size() < 2:
                raise ValueError("Invalid expression")
            
            b = stack.pop()  # 注意順序
            a = stack.pop()
            
            if token == '+':
                result = a + b
            elif token == '-':
                result = a - b
            elif token == '*':
                result = a * b
            elif token == '/':
                result = a / b
            
            stack.push(result)
        else:
            # 運算元:push 進 stack
            stack.push(float(token))
    
    if stack.size() != 1:
        raise ValueError("Invalid expression")
    
    return stack.pop()

# 測試
expressions = [
    ("2 3 +", 5),              # 2 + 3
    ("2 3 + 5 *", 25),         # (2 + 3) * 5
    ("5 1 2 + 4 * + 3 -", 14), # 5 + ((1 + 2) * 4) - 3
]

for expr, expected in expressions:
    result = evaluate_postfix(expr)
    print(f"'{expr}' = {result} (expected: {expected})")

經典應用:中序轉後序表達式

def infix_to_postfix(expression):
    """
    將中序表達式轉換為後序表達式
    例如: "2 + 3 * 5" → "2 3 5 * +"
    """
    precedence = {
        '+': 1,
        '-': 1,
        '*': 2,
        '/': 2,
        '^': 3
    }
    
    stack = Stack()
    output = []
    
    for token in expression.split():
        if token.isdigit():
            # 數字直接輸出
            output.append(token)
        elif token == '(':
            # 左括號 push 進 stack
            stack.push(token)
        elif token == ')':
            # 右括號:pop 直到遇到左括號
            while not stack.is_empty() and stack.peek() != '(':
                output.append(stack.pop())
            if not stack.is_empty():
                stack.pop()  # 移除左括號
        elif token in precedence:
            # 運算符:處理優先級
            while (not stack.is_empty() and 
                   stack.peek() != '(' and
                   stack.peek() in precedence and
                   precedence[stack.peek()] >= precedence[token]):
                output.append(stack.pop())
            stack.push(token)
    
    # Pop 剩餘的運算符
    while not stack.is_empty():
        output.append(stack.pop())
    
    return ' '.join(output)

# 測試
test_cases = [
    "2 + 3",                    # "2 3 +"
    "2 + 3 * 5",               # "2 3 5 * +"
    "( 2 + 3 ) * 5",           # "2 3 + 5 *"
    "2 + 3 * 5 - 4",           # "2 3 5 * + 4 -"
]

for infix in test_cases:
    postfix = infix_to_postfix(infix)
    print(f"Infix:   {infix}")
    print(f"Postfix: {postfix}")
    print(f"Result:  {evaluate_postfix(postfix)}\n")

實際應用範例

1. 瀏覽器歷史記錄

class BrowserHistory:
    """瀏覽器歷史記錄管理"""
    
    def __init__(self):
        self.back_stack = Stack()      # 儲存歷史頁面
        self.forward_stack = Stack()   # 儲存前進頁面
        self.current_page = None
    
    def visit(self, url):
        """訪問新頁面"""
        if self.current_page:
            self.back_stack.push(self.current_page)
        
        self.current_page = url
        # 訪問新頁面時,清空 forward stack
        self.forward_stack = Stack()
        
        print(f"Visited: {url}")
    
    def back(self):
        """返回上一頁"""
        if self.back_stack.is_empty():
            print("No previous page")
            return None
        
        self.forward_stack.push(self.current_page)
        self.current_page = self.back_stack.pop()
        print(f"Back to: {self.current_page}")
        return self.current_page
    
    def forward(self):
        """前進到下一頁"""
        if self.forward_stack.is_empty():
            print("No forward page")
            return None
        
        self.back_stack.push(self.current_page)
        self.current_page = self.forward_stack.pop()
        print(f"Forward to: {self.current_page}")
        return self.current_page
    
    def show_status(self):
        """顯示當前狀態"""
        print(f"\nCurrent: {self.current_page}")
        print(f"Can go back: {not self.back_stack.is_empty()}")
        print(f"Can go forward: {not self.forward_stack.is_empty()}")

# 使用範例
browser = BrowserHistory()
browser.visit("google.com")
browser.visit("youtube.com")
browser.visit("github.com")
browser.show_status()

browser.back()  # 回到 youtube.com
browser.back()  # 回到 google.com
browser.forward()  # 前進到 youtube.com
browser.visit("stackoverflow.com")  # 清空 forward stack
browser.show_status()

2. 函式呼叫追蹤

class CallStack:
    """模擬函式呼叫堆疊"""
    
    def __init__(self):
        self.stack = Stack()
        self.depth = 0
    
    def call(self, function_name, params=None):
        """模擬函式呼叫"""
        self.stack.push({
            'function': function_name,
            'params': params,
            'depth': self.depth
        })
        print("  " * self.depth + f"→ Calling {function_name}({params})")
        self.depth += 1
    
    def return_from(self, function_name, result=None):
        """模擬函式返回"""
        if self.stack.is_empty():
            raise RuntimeError("Call stack is empty")
        
        self.depth -= 1
        call_info = self.stack.pop()
        
        if call_info['function'] != function_name:
            raise RuntimeError(f"Expected to return from {call_info['function']}")
        
        print("  " * self.depth + f"← Returning from {function_name}: {result}")
        return result
    
    def print_stack_trace(self):
        """印出堆疊追蹤"""
        print("\nStack Trace:")
        
        # 暫存所有元素
        temp = []
        while not self.stack.is_empty():
            temp.append(self.stack.pop())
        
        # 印出並恢復
        for i, call in enumerate(reversed(temp)):
            print(f"  {i}: {call['function']}({call['params']})")
        
        # 恢復 stack
        for call in reversed(temp):
            self.stack.push(call)

# 使用範例:模擬遞迴
def simulate_factorial(n):
    call_stack = CallStack()
    
    def factorial_with_stack(n):
        call_stack.call("factorial", n)
        
        if n <= 1:
            result = 1
        else:
            result = n * factorial_with_stack(n - 1)
        
        call_stack.return_from("factorial", result)
        return result
    
    try:
        result = factorial_with_stack(n)
        print(f"\nfactorial({n}) = {result}")
    except RecursionError:
        print("Stack overflow!")
        call_stack.print_stack_trace()

simulate_factorial(5)

3. 文字編輯器的 Undo/Redo

class TextEditor:
    """支援 Undo/Redo 的文字編輯器"""
    
    def __init__(self):
        self.text = ""
        self.undo_stack = Stack()
        self.redo_stack = Stack()
    
    def write(self, text):
        """輸入文字"""
        # 儲存當前狀態到 undo stack
        self.undo_stack.push(self.text)
        
        # 清空 redo stack
        self.redo_stack = Stack()
        
        # 更新文字
        self.text += text
        print(f"Wrote: '{text}'")
        self.show()
    
    def delete(self, count):
        """刪除最後 count 個字元"""
        if count > len(self.text):
            count = len(self.text)
        
        # 儲存當前狀態
        self.undo_stack.push(self.text)
        self.redo_stack = Stack()
        
        # 刪除文字
        deleted = self.text[-count:]
        self.text = self.text[:-count]
        print(f"Deleted: '{deleted}'")
        self.show()
    
    def undo(self):
        """復原上一個操作"""
        if self.undo_stack.is_empty():
            print("Nothing to undo")
            return
        
        # 儲存當前狀態到 redo stack
        self.redo_stack.push(self.text)
        
        # 恢復前一個狀態
        self.text = self.undo_stack.pop()
        print("Undo performed")
        self.show()
    
    def redo(self):
        """重做上一個復原的操作"""
        if self.redo_stack.is_empty():
            print("Nothing to redo")
            return
        
        # 儲存當前狀態到 undo stack
        self.undo_stack.push(self.text)
        
        # 恢復下一個狀態
        self.text = self.redo_stack.pop()
        print("Redo performed")
        self.show()
    
    def show(self):
        """顯示當前文字"""
        print(f"Text: '{self.text}'")
        print(f"Can undo: {not self.undo_stack.is_empty()}")
        print(f"Can redo: {not self.redo_stack.is_empty()}\n")

# 使用範例
editor = TextEditor()
editor.write("Hello")
editor.write(" World")
editor.write("!")

editor.undo()  # 回到 "Hello World"
editor.undo()  # 回到 "Hello"

editor.redo()  # 前進到 "Hello World"
editor.write(" Python")  # 清空 redo stack

editor.delete(7)  # 刪除 " Python"
editor.undo()     # 恢復

4. 深度優先搜尋(DFS)

def dfs_iterative(graph, start):
    """使用 Stack 實作的迭代式 DFS"""
    visited = set()
    stack = Stack()
    path = []
    
    stack.push(start)
    
    while not stack.is_empty():
        vertex = stack.pop()
        
        if vertex not in visited:
            visited.add(vertex)
            path.append(vertex)
            
            # 將鄰居加入 stack(反向加入以保持順序)
            for neighbor in reversed(graph[vertex]):
                if neighbor not in visited:
                    stack.push(neighbor)
    
    return path

# 測試圖
graph = {
    'A': ['B', 'C'],
    'B': ['D', 'E'],
    'C': ['F'],
    'D': [],
    'E': ['F'],
    'F': []
}

# 比較遞迴和迭代式 DFS
def dfs_recursive(graph, start, visited=None, path=None):
    """遞迴式 DFS(用於比較)"""
    if visited is None:
        visited = set()
        path = []
    
    visited.add(start)
    path.append(start)
    
    for neighbor in graph[start]:
        if neighbor not in visited:
            dfs_recursive(graph, neighbor, visited, path)
    
    return path

print("DFS Traversal:")
print(f"Iterative: {dfs_iterative(graph, 'A')}")
print(f"Recursive: {dfs_recursive(graph, 'A')}")

進階主題

1. Min Stack(支援 O(1) 取得最小值)

class MinStack:
    """支援 O(1) 時間取得最小值的 Stack"""
    
    def __init__(self):
        self.stack = Stack()
        self.min_stack = Stack()  # 儲存最小值
    
    def push(self, item):
        """Push - O(1)"""
        self.stack.push(item)
        
        # 更新最小值
        if self.min_stack.is_empty() or item <= self.min_stack.peek():
            self.min_stack.push(item)
    
    def pop(self):
        """Pop - O(1)"""
        if self.stack.is_empty():
            raise IndexError("Stack is empty")
        
        item = self.stack.pop()
        
        # 如果 pop 的是最小值,也要從 min_stack pop
        if item == self.min_stack.peek():
            self.min_stack.pop()
        
        return item
    
    def get_min(self):
        """取得最小值 - O(1)"""
        if self.min_stack.is_empty():
            raise ValueError("Stack is empty")
        return self.min_stack.peek()
    
    def peek(self):
        return self.stack.peek()
    
    def is_empty(self):
        return self.stack.is_empty()
    
    def __str__(self):
        return f"MinStack: {self.stack.items}, Min: {self.get_min() if not self.is_empty() else 'N/A'}"

# 測試
min_stack = MinStack()
values = [5, 2, 7, 2, 1, 8]

for val in values:
    min_stack.push(val)
    print(f"Push {val}: Min = {min_stack.get_min()}")

print()
while not min_stack.is_empty():
    print(f"Pop {min_stack.pop()}: Min = {min_stack.get_min() if not min_stack.is_empty() else 'N/A'}")

2. 使用兩個 Stack 實作 Queue

class QueueUsingTwoStacks:
    """使用兩個 Stack 實作 Queue"""
    
    def __init__(self):
        self.in_stack = Stack()   # 用於 enqueue
        self.out_stack = Stack()  # 用於 dequeue
    
    def enqueue(self, item):
        """入隊 - O(1)"""
        self.in_stack.push(item)
    
    def dequeue(self):
        """出隊 - 平均 O(1),最壞 O(n)"""
        # 如果 out_stack 是空的,將 in_stack 的元素轉移過來
        if self.out_stack.is_empty():
            while not self.in_stack.is_empty():
                self.out_stack.push(self.in_stack.pop())
        
        if self.out_stack.is_empty():
            raise IndexError("Queue is empty")
        
        return self.out_stack.pop()
    
    def is_empty(self):
        return self.in_stack.is_empty() and self.out_stack.is_empty()
    
    def size(self):
        return self.in_stack.size() + self.out_stack.size()

# 測試
queue = QueueUsingTwoStacks()
for i in range(1, 4):
    queue.enqueue(i)
    print(f"Enqueued: {i}")

print(f"Dequeued: {queue.dequeue()}")  # 1
queue.enqueue(4)
print(f"Dequeued: {queue.dequeue()}")  # 2

效能分析

時間複雜度

操作List 實作Linked List 實作說明
push()O(1)*O(1)*平均情況,可能需要擴容
pop()O(1)O(1)從頂端操作
peek()O(1)O(1)只查看不移除
is_empty()O(1)O(1)檢查大小
size()O(1)O(1)維護計數器

空間複雜度

  • List 實作:O(n),可能有預留空間
  • Linked List 實作:O(n),每個節點需要額外指標
  • 固定大小陣列:O(capacity)

Stack vs Queue 比較

特性Stack (LIFO)Queue (FIFO)
新增元素Push(頂端)Enqueue(尾端)
移除元素Pop(頂端)Dequeue(前端)
應用場景函式呼叫、括號匹配任務排程、BFS
實作複雜度簡單稍複雜(需要兩端操作)

常見面試題型

# 1. 檢查字串是否為回文(使用 Stack)
def is_palindrome_with_stack(s):
    """使用 Stack 檢查回文"""
    stack = Stack()
    
    # 將前半部分 push 進 stack
    mid = len(s) // 2
    for i in range(mid):
        stack.push(s[i])
    
    # 如果長度是奇數,跳過中間字元
    start = mid + 1 if len(s) % 2 == 1 else mid
    
    # 比較後半部分
    for i in range(start, len(s)):
        if stack.is_empty() or stack.pop() != s[i]:
            return False
    
    return stack.is_empty()

# 2. 簡化檔案路徑
def simplify_path(path):
    """
    簡化 Unix 風格的檔案路徑
    例如: "/a/./b/../../c/" → "/c"
    """
    stack = Stack()
    parts = path.split('/')
    
    for part in parts:
        if part == '..':
            if not stack.is_empty():
                stack.pop()
        elif part and part != '.':
            stack.push(part)
    
    # 重建路徑
    result = []
    while not stack.is_empty():
        result.append(stack.pop())
    
    return '/' + '/'.join(reversed(result))

# 測試
test_paths = [
    "/home/",
    "/../",
    "/home//foo/",
    "/a/./b/../../c/",
]

for path in test_paths:
    print(f"'{path}' → '{simplify_path(path)}'")

總結

Stack 是程式設計中最基礎且重要的資料結構之一:

核心特點

  1. LIFO 原則:後進先出
  2. 簡單高效:所有操作都是 O(1)
  3. 用途廣泛:從系統底層到應用層都有使用

重要應用

  • 系統層面:函式呼叫堆疊、記憶體管理
  • 演算法:DFS、回溯法、表達式求值
  • 應用程式:撤銷/重做、括號匹配、瀏覽器歷史

實作建議

  • 一般用途:使用 Python list
  • 教學/面試:理解 Linked List 實作
  • 特殊需求:考慮固定大小或特殊功能(如 MinStack)

掌握 Stack 的概念和實作,是理解更複雜資料結構和演算法的重要基礎!

0%