# 用 Java 學資料結構:堆疊 Stack ## 什麼是 Stack? 堆疊(Stack)是一種基本的資料結構,遵循著一個簡單的原則:**後進先出**(Last In, First Out, LIFO)。這意味著最後加入的元素會最先被取出。就好像我們將磚頭一塊疊在前一塊的上面,當我們要拿磚頭的時候,就會先拿最上面的那塊。這種特性讓我們可以在很多場景中使用堆疊,例如括瀏覽器的返回、括號匹配等。 ## Stack 的基本操作 以下會用 Java 來說明 Stack 的基本操作,包括 push、pop、peek、isEmpty 與 size。 首先,我們先來宣告 Stack 的物件,在宣告時,我們需要指定 Stack 的元素類型,例如整數類型: ```java Stack<Integer> stack = new Stack<>(); ``` > 在 Java 7 之前,我們需要使用 `new Stack<Integer>()` 來宣告 Stack 物件,但在 Java 7 之後,可以使用鑽石運算子(Diamond Operator)`<>`,讓編譯器自動推斷型別。比較簡潔。 ### 常見操作方法 #### push: 將元素加入到 Stack 頂部。 ```java stack.push(1); stack.push(2); stack.push(3); stack.push(4); stack.push(5); System.out.println(stack); // 輸出:[1, 2, 3, 4, 5] ``` #### pop: 移除並返回頂部的元素。 ```java int top = stack.pop(); System.out.println(top); // 輸出:5 System.out.println(stack); // 輸出:[1, 2, 3, 4] ``` #### peek: 獲取但不移除頂部的元素。 ```java int top = stack.peek(); System.out.println(top); // 輸出:4 System.out.println(stack); // 輸出:[1, 2, 3, 4] ``` #### isEmpty: 檢查 Stack 是否為空。 ```java boolean empty = stack.isEmpty(); System.out.println(empty); // 輸出:false ``` #### size: 返回 Stack 的當前大小。 ```java int size = stack.size(); System.out.println(size); // 輸出:4 ``` #### search: 搜索元素在 Stack 中的位置。 - 位置是從 Stack 頂部開始計算的,會從 1 開始。如果元素不存在,則返回 -1。 ```java int position = stack.search(3); System.out.println(position); // 輸出:2 ``` - 在我們的範例中,經過如下的操作 - 由下往上堆疊 1、2、3、4、5 ```ascii ┌────┐ │ 5 │ <- top ├────┤ │ 4 │ ├────┤ │ 3 │ ├────┤ │ 2 │ ├────┤ │ 1 │ └────┘ ``` - 取出頂部的元素 5,剩下由下往上堆疊的元素 1、2、3、4 ```ascii ┌────┐ │ 4 │ <- top (pop移除5) ├────┤ │ 3 │ ├────┤ │ 2 │ ├────┤ │ 1 │ └────┘ ``` - 查看頂部的元素 4,此時 Stack 中堆疊的元素依然是 1、2、3、4 - 搜索元素 3 的位置,從頂部低一個元素開始計算,所以位置是 2 ```ascii ┌────┐ │ 4 │ <- top, 位置 1 ├────┤ │ 3 │ <- 位置 2,search(3) ├────┤ │ 2 │ <- 位置 3 ├────┤ │ 1 │ <- 位置 4 └────┘ ``` ### 這些操作的時間複雜度 - push、pop、peek、isEmpty、size 操作的時間複雜度都是 O(1)。 - 表示這些操作的時間與 Stack 中的元素個數無關,只與操作本身的複雜度有關。 - search 操作的時間複雜度是 O(n)。 - 表示 search 操作的時間與 Stack 中的元素個數成正比。 ## Java 中的 Stack 實現方式 ### 使用 Java 提供的 Stack 類 介紹 Java 的 java.util.Stack,並提供簡單的程式範例。 ### 使用 Array 實現 Stack 用 Array 實現一個自訂的 Stack,並提供完整範例。 1. 首先,我們需要定義 Stack 的大小與元素數組。 2. 然後,我們需要實現 Stack 的基本操作,包括 push、pop、peek、isEmpty 與 size。 ```java public class ArrayStack { private int maxSize; private int[] stack; private int top = -1; // 初始化為 -1,表示 Stack 是空的 public ArrayStack(int maxSize) { this.maxSize = maxSize; stack = new int[maxSize]; } public void push(int value) { if (isFull()) { System.out.println("Stack is full."); return; } stack[++top] = value; } public int pop() { if (isEmpty()) { throw new RuntimeException("Stack is empty."); } return stack[top--]; } public int peek() { return stack[top]; } public boolean isEmpty() { return top == -1; } public boolean isFull() { return top == maxSize - 1; } public int size() { return top + 1; } @Override public String toString() { return Arrays.toString(Arrays.copyOfRange(stack, 0, top + 1)); } } ``` 3. 最後,我們可以使用這個自訂的 Stack 類。 ```java ArrayStack stack = new ArrayStack(5); stack.push(1); stack.push(2); stack.push(3); stack.push(4); stack.push(5); System.out.println(stack); // 輸出:[1, 2, 3, 4, 5] int top = stack.pop(); System.out.println(top); // 輸出:5 System.out.println(stack); // 輸出:[1, 2, 3, 4] ``` ### 使用 Linked List 實現 Stack 用 Linked List 實現一個自訂的 Stack,並提供完整範例。 1. 首先,我們需要定義 Node 類。 2. 然後,我們需要實現 Stack 的基本操作,包括 push、pop、peek、isEmpty 與 size。 ```java public class Node { public int value; public Node next; public Node(int value) { this.value = value; } } public class LinkedListStack { private Node top; public void push(int value) { Node node = new Node(value); node.next = top; top = node; } public int pop() { if (isEmpty()) { throw new RuntimeException("Stack is empty."); } int value = top.value; top = top.next; return value; } public int peek() { if (isEmpty()) { throw new RuntimeException("Stack is empty."); } return top.value; } public boolean isEmpty() { return top == null; } public int size() { int size = 0; Node current = top; while (current != null) { size++; current = current.next; } return size; } @Override public String toString() { StringBuilder sb = new StringBuilder(); Node current = top; while (current != null) { sb.append(current.value).append(" "); current = current.next; } return sb.toString(); } } ``` ## Stack 的應用範例 ### 包含大中小括號的加減乘除運算 使用 Stack 實現包含大中小括號的加減乘除運算的範例。 ```java import java.util.Stack; public class Calculator { public static double calculate(String expression) { // 擺放數字的堆疊 Stack<Double> numbers = new Stack<>(); // 擺放運算符的堆疊 Stack<Character> operators = new Stack<>(); // 遍歷表達式中的每個字符 for (int i = 0; i < expression.length(); i++) { // 獲取當前字符 char ch = expression.charAt(i); // 如果是空白字符,則忽略,結束當前循環,進入下一個循環 if (Character.isWhitespace(ch)) { continue; } if (Character.isDigit(ch) || ch == '.') { // 如果是數字或是小數點的話 StringBuilder num = new StringBuilder(); while (i < expression.length() && // 當前的字符位置小於表達式的長度,表示後面還有字符,且 (Character.isDigit(expression.charAt(i)) || // 當前的字符是數字 或 expression.charAt(i) == '.')) { // 當前的字符是小數點 num.append(expression.charAt(i)); // 將當前的字符加入到 num 中,表示正在組合一個完整的數字。如 12或12.34 i++; // 繼續下一個字符 } i--; // 因為上面的 i++,所以這裡要減 1,才能保證下一次循環時,i 指向正確的字符。例如:12+34,i 在 1 的位置,下一次循環時,i 應該指向 + 的位置。 numbers.push(Double.parseDouble(num.toString())); // 將組合好的數字轉換為 double 類型,並加入到 numbers 堆疊中 } else if (ch == '(' || ch == '[' || ch == '{') { // 如果是左括號的話 operators.push(ch); // 直接加入到 operators 堆疊中 } else if (ch == ')' || ch == ']' || ch == '}') { // 如果是右括號的話 while (!operators.isEmpty() && // 如果 operators 堆疊不為空,且 !isOpenBracket(operators.peek())) { // 當前的字符不是左括號的話 processOperation(numbers, operators); // 則處理運算 } if (!operators.isEmpty() && isMatchingBracket(operators.peek(), ch)) { // 如果 operators 堆疊不為空,且當前的字符是左右括號匹配的話 operators.pop(); // 則將左括號從 operators 堆疊中移除 } else { throw new RuntimeException("括號不匹配"); } } else if (isOperator(ch)) { // 如果是運算符的話 while (!operators.isEmpty() && // 如果 operators 堆疊不為空,且 !isOpenBracket(operators.peek()) && // 當前的字符不是左括號,且 getPrecedence(operators.peek()) >= getPrecedence(ch)) { // 當前的字符的優先級小於等於 operators 堆疊頂部的運算符的優先級的話 processOperation(numbers, operators); // 則處理運算 } operators.push(ch); // 將當前的字符加入到 operators 堆疊中 } } while (!operators.isEmpty()) { // 當 operators 堆疊不為空的話 if (isOpenBracket(operators.peek())) { // 如果 operators 堆疊頂部的字符是左括號的話 throw new RuntimeException("括號不匹配"); // 表示括號不匹配 } processOperation(numbers, operators); // 則處理運算 } if (numbers.size() != 1) { // 如果 numbers 堆疊的大小不為 1 的話 throw new RuntimeException("表達式無效"); // 表示表達式無效 } return numbers.pop(); // 返回 numbers 堆疊頂部的元素,即計算結果 } private static boolean isOpenBracket(char ch) { return ch == '(' || ch == '[' || ch == '{'; } private static boolean isMatchingBracket(char open, char close) { return (open == '(' && close == ')') || (open == '[' && close == ']') || (open == '{' && close == '}'); } private static boolean isOperator(char ch) { return ch == '+' || ch == '-' || ch == '*' || ch == '/'; } private static int getPrecedence(char operator) { switch (operator) { case '+': case '-': return 1; case '*': case '/': return 2; default: return 0; } } private static void processOperation(Stack<Double> numbers, Stack<Character> operators) { if (numbers.size() < 2) { throw new RuntimeException("表達式無效"); } double b = numbers.pop(); double a = numbers.pop(); char operator = operators.pop(); switch (operator) { case '+': numbers.push(a + b); break; case '-': numbers.push(a - b); break; case '*': numbers.push(a * b); break; case '/': if (b == 0) { throw new RuntimeException("除數不能為零"); } numbers.push(a / b); break; default: throw new RuntimeException("未知運算符"); } } public static void main(String[] args) { // 測試範例 String[] tests = { "1 + 2 * 3", // 7.0 "(1 + 2) * 3", // 9.0 "{1 + [2 * (3 + 4)]}", // 15.0 "2 * (3 + 4) / 2" // 7.0 }; for (String test : tests) { try { System.out.println(test + " = " + calculate(test)); } catch (Exception e) { System.out.println(test + " 錯誤: " + e.getMessage()); } } } } ```