# Data Structure / Stack ## 1. 基本概念 堆疊(Stack)是一種遵循「後進先出」(Last In First Out,LIFO)原則的資料結構。我們可以將堆疊想像成一疊盤子:你只能從最上面放入或拿走盤子,無法直接觸及中間或底部的盤子。 ## 2. 基本操作 1. **`push()`**:將新元素推入堆疊頂端 2. **`pop()`**:從堆疊頂端彈出元素 3. **`peek()`**:查看堆疊頂端的元素,但不移除 ## 3. 堆疊的實作方式 先定義一個堆疊的基本介面ㄅ: ```java= interface Stack<T> { void push(T item) throws StackOverflowException; T pop() throws StackUnderflowException; T peek() throws StackUnderflowException; boolean isEmpty(); boolean isFull(); } ``` ### 3.1 使用陣列實作(Array-Based Implementation) 以下是使用陣列實作佇列的範例程式碼: ```java= class ArrayStack<T> implements Stack<T> { private T[] stack; private int top; private final int capacity; @SuppressWarnings("unchecked") public ArrayStack(int capacity) { this.capacity = capacity; stack = (T[]) new Object[capacity]; top = -1; } @Override public void push(T item) throws StackOverflowException { if (isFull()) { throw new StackOverflowException("堆疊已滿"); } stack[++top] = item; } @Override public T pop() throws StackUnderflowException { if (isEmpty()) { throw new StackUnderflowException("堆疊是空的"); } T item = stack[top]; stack[top--] = null; // 避免記憶體洩漏 return item; } @Override public T peek() throws StackUnderflowException { if (isEmpty()) { throw new StackUnderflowException("堆疊是空的"); } return stack[top]; } @Override public boolean isEmpty() { return top == -1; } @Override public boolean isFull() { return top == capacity - 1; } } // customize Exceptions class StackOverflowException extends Exception { public StackOverflowException(String message) { super(message); } } class StackUnderflowException extends Exception { public StackUnderflowException(String message) { super(message); } } ``` ### 3.2 使用鏈結串列實作(Linked List Implementation) 以下是使用循環鏈結串列實作佇列的範例: ```java= class LinkedStack<T> implements Stack<T> { private static class Node<T> { T data; Node<T> next; Node(T data) { this.data = data; this.next = null; } } private Node<T> top; public LinkedStack() { top = null; } @Override public void push(T item) { Node<T> newNode = new Node<>(item); newNode.next = top; top = newNode; } @Override public T pop() throws StackUnderflowException { if (isEmpty()) { throw new StackUnderflowException("堆疊是空的"); } T item = top.data; top = top.next; return item; } @Override public T peek() throws StackUnderflowException { if (isEmpty()) { throw new StackUnderflowException("堆疊是空的"); } return top.data; } @Override public boolean isEmpty() { return top == null; } @Override public boolean isFull() { return false; // 鏈結串列實作的堆疊永遠不會滿 } } ``` --- #### 所以泥在 Main Class 裡面就可以醬: ```java= public class StackDemo { public static void main(String[] args) { try { // 使用陣列實作的堆疊 Stack<Integer> arrayStack = new ArrayStack<>(5); arrayStack.push(1); arrayStack.push(2); arrayStack.push(3); System.out.println("Pop: " + arrayStack.pop()); // 3 System.out.println("Peek: " + arrayStack.peek()); // 2 // 使用鏈結串列實作的堆疊 LinkedStack<String> linkedStack = new LinkedStack<>(); linkedStack.push("Hello"); linkedStack.push("World"); System.out.println("Pop: " + linkedStack.pop()); // World System.out.println("Peek: " + linkedStack.peek()); // Hello } catch (StackOverflowException | StackUnderflowException e) { System.err.println("堆疊操作錯誤: " + e.getMessage()); } } } ``` ## 4. 堆疊的應用 1. 函式呼叫的管理 - 系統使用堆疊來追蹤函式的呼叫順序 - 儲存函式的區域變數和返回位址 2. [表達式求值](#堆疊在運算式處理中的應用) - 中序、前序、後序運算式的轉換 - 運算式的計算 3. 編輯器的復原功能 - 儲存操作歷史 - 實現 Undo/Redo 功能 4. 瀏覽器的網頁歷史 - 回到上一頁/下一頁功能 5. 括號匹配檢查 ```java= public class BracketChecker { public static boolean isBalanced(String expression) { LinkedStack<Character> stack = new LinkedStack<>(); Map<Character, Character> brackets = new HashMap<>(); brackets.put(')', '('); brackets.put('}', '{'); brackets.put(']', '['); try { for (char ch : expression.toCharArray()) { if ("({[".contains(String.valueOf(ch))) { stack.push(ch); } else if (")}]".contains(String.valueOf(ch))) { if (stack.isEmpty() || stack.pop() != brackets.get(ch)) { return false; } } } return stack.isEmpty(); } catch (StackUnderflowException e) { return false; } } public static void main(String[] args) { String expr1 = "({[]})"; String expr2 = "({[})"; System.out.println("表達式 " + expr1 + " 是否成對: " + isBalanced(expr1)); // true System.out.println("表達式 " + expr2 + " 是否成對: " + isBalanced(expr2)); // false } } ``` ## 5. 使用堆疊時的注意事項 1. 堆疊溢位(Stack Overflow,aka [Stack Overflow](https://stackoverflow.com/)) - 確保不會超過堆疊容量 - 考慮使用動態大小的實作方式 2. 空堆疊處理 - 在 `pop()` 或 `peek()` 操作前檢查堆疊是否為空 - 適當的例外處理 3. 記憶體管理 - 適時釋放不需要的記憶體 - 避免記憶體洩漏 4. 如果是使用 JDK 的 `Stack` 類別,它是繼承自 `Vector`,在某些效能考量的情況下可能更適合使用 `ArrayDeque` 作為堆疊。 # 堆疊在運算式處理中的應用 由於電腦無法像人腦一樣,能夠在算式之間就進行先乘除後加減的優先級判斷與排序,因此才會設計出一套規則,能夠讓電腦step by step的處理好加減乘除。 ## 1. 運算式的表示方法 在開始之前,讓我們先了解三種主要的運算式表示方法: 1. **中序表示法(Infix)**:這是我們最常用的表示方法,運算子放在兩個運算元之間,如 `A + B * C`。 2. **後序表示法(Postfix)**:也稱為逆波蘭表示法,運算子放在相關的運算元後面,如 `A B C * +`。 3. **前序表示法(Prefix)**:也稱為波蘭表示法,運算子放在相關的運算元前面,如 `+ A * B C`。 ## 2. 中序轉後序(Infix to Postfix) ### 2.1 轉換原理 將中序式轉換為後序式時,我們需要: 1. 由左至右掃描運算式 2. 使用一個運算子堆疊(operator stack)來暫存運算子 3. 根據運算子優先順序決定輸出順序 ### 2.2 轉換規則 1. 遇到運算元(operand)直接輸出 2. 遇到運算子(operator)時: - 若堆疊為空,直接將運算子推入堆疊 - 比較目前運算子與堆疊頂端運算子的優先順序 - 若目前運算子優先順序 **較高**,則推入堆疊 - 若目前運算子優先順序 **較低或相等**,則將堆疊頂端運算子輸出,直到找到優先順序 **較低** 的運算子 ### 2.3 範例說明 以 `A + B * C / D - E` 為例: ```plain 掃描順序 堆疊狀態 輸出結果 A 空 A + + A B + AB * +* AB C +* ABC / +/ ABC* D +/ ABC*D - - ABC*D/+ E - ABC*D/+E 結束 空 ABC*D/+E- ``` ### 2.4 範例程式碼(python) ```python= def infix_to_postfix(expression): precedence = {'+': 1, '-': 1, '*': 2, '/': 2, '^': 3} operators = [] output = [] for char in expression: if char.isalnum(): output.append(char) elif char == '(': operators.append(char) elif char == ')': while operators and operators[-1] != '(': output.append(operators.pop()) operators.pop() # 移除 '(' else: while (operators and operators[-1] != '(' and precedence.get(operators[-1], 0) >= precedence.get(char, 0)): output.append(operators.pop()) operators.append(char) while operators: output.append(operators.pop()) return ''.join(output) # 測試範例 expr = "A+B*C/D-E" print(infix_to_postfix(expr)) # 輸出:ABC*D/+E- ``` ## 3. 中序轉前序(Infix to Prefix) ### 3.1 轉換原理 中序轉前序的過程與轉後序類似,但是: 1. 需要由右至左掃描運算式 2. 輸出時從左邊開始堆疊 3. 運算子優先順序的判斷方式略有不同 ### 3.2 轉換規則 1. 遇到運算元(operand)直接輸出 2. 遇到運算子(operator)時: - 若堆疊為空,直接將運算子推入堆疊 - 比較目前運算子與堆疊頂端運算子的優先順序 - 若目前運算子優先順序 **較高**,則推入堆疊 - 若目前運算子優先順序 **較低**,則將堆疊頂端運算子輸出,直到找到優先順序 **相同** 的運算子 ### 3.3 範例說明 以 `A + B * C / D - E` 為例: ```plain 掃描順序 堆疊狀態 輸出結果 E 空 E - - E D - DE / -/ DE C -/* CDE * -/* BCDE B -+ /*BCDE + -+ A/*BCDE A 空 -+A/*BCDE ``` ## 4. 運算式求值 ### 4.1 後序式求值 後序式求值的過程相對簡單: 1. 由左至右掃描運算式 2. 遇到運算元則推入堆疊 3. 遇到運算子則從堆疊取出兩個運算元進行運算,將結果推回堆疊 #### 範例程式碼(python): ```python= def evaluate_postfix(expression): stack = [] for token in expression: if token.isdigit(): stack.append(int(token)) else: b = stack.pop() a = stack.pop() if token == '+': stack.append(a + b) elif token == '-': stack.append(a - b) elif token == '*': stack.append(a * b) elif token == '/': stack.append(a / b) return stack[0] # 範例:評估 "47+82-*" print(evaluate_postfix("47+82-*")) # 結果應為 66 ``` ### 4.2 前序式求值 前序式求值的過程與後序式類似,但需要: 1. 由右至左掃描運算式 2. 運算時注意運算元的順序 #### 範例程式碼(python): ```python= def evaluate_prefix(expression): stack = [] for token in reversed(expression): # 前序式需從右到左掃描 if token.isdigit(): stack.append(int(token)) else: a = stack.pop() # 注意:前序式彈出順序與後序式相反 b = stack.pop() if token == '+': stack.append(a + b) elif token == '-': stack.append(a - b) elif token == '*': stack.append(a * b) elif token == '/': stack.append(a / b) return stack[0] # 範例:評估 "*+47-82" (相當於 (4+7)*(8-2)) print(evaluate_prefix("*+47-82")) # 結果應為 66 ``` ### 4.3 中序式求值 中序式求值需要兩個堆疊: 1. 運算元堆疊(operand stack) 2. 運算子堆疊(operator stack) 處理過程較為複雜,需要考慮: 1. 運算子優先順序 2. 括號的處理 3. 運算元的順序 #### 範例程式碼(python): ```python= def evaluate_infix(expression): operators = [] values = [] for token in expression: if token.isdigit(): values.append(int(token)) elif token in "+-*/": while (operators and operators[-1] in "+-*/" and precedence(operators[-1]) >= precedence(token)): process_operator(operators, values) operators.append(token) elif token == '(': operators.append(token) elif token == ')': while operators and operators[-1] != '(': process_operator(operators, values) operators.pop() # 移除 '(' while operators: process_operator(operators, values) return values[0] def precedence(op): if op in "+-": return 1 if op in "*/": return 2 return 0 def process_operator(operators, values): op = operators.pop() b = values.pop() a = values.pop() if op == '+': values.append(a + b) elif op == '-': values.append(a - b) elif op == '*': values.append(a * b) elif op == '/': values.append(a / b) # 範例:評估 "4+7*8-2" 和 "(4+7)*(8-2)" print(evaluate_infix("4+7*8-2")) # 結果應為 58 print(evaluate_infix("(4+7)*(8-2)")) # 結果應為 66 ``` ## 5. 特殊考慮事項 1. 括號處理: - 左括號 `(` 直接推入運算子堆疊 - 遇到右括號 `)` 時,不斷輸出運算子直到遇到左括號 2. 運算子優先順序: - 括號最高優先順序 - 乘除(*、/) > 加減(+、-) - 同級運算子從左到右運算 3. 錯誤處理: - 運算式語法檢查 - 除數為零的檢查 - 堆疊溢出的處理