遞歸是將複雜問題分解為同類子問題的經典算法策略。本文通過階乘計算實例,從代碼實現、堆棧內存模型、字節碼層面三個維度深度解析遞歸執行機制。
public class RecursionDemo {
/**
* 遞歸計算階乘
* @param n 非負整數
* @return n!
*/
public static int factorial(int n) {
// 基線條件
if (n == 0 || n == 1) {
return 1;
}
// 遞歸步驟:問題規模縮小
return n * factorial(n - 1);
}
public static void main(String[] args) {
System.out.println("5! = " + factorial(5)); // 輸出 120
}
}
以 factorial(5) 為例,展示運行時堆棧變化:
調用棧狀態(棧頂在下): ┌─────────────────────────────────────┐ │ n=1 | factorial(1) | return 1 │ ← 基線條件,開始回溯 ├─────────────────────────────────────┤ │ n=2 | factorial(2) | 2 * factorial(1)│ ├─────────────────────────────────────┤ │ n=3 | factorial(3) | 3 * factorial(2)│ ├─────────────────────────────────────┤ │ n=4 | factorial(4) | 4 * factorial(3)│ ├─────────────────────────────────────┤ │ n=5 | factorial(5) | 5 * factorial(4)│ ← 初始調用 └─────────────────────────────────────┘
factorial(1) 返回 1factorial(2) = 2 × 1 = 2factorial(3) = 3 × 2 = 6factorial(4) = 4 × 6 = 24factorial(5) = 5 × 24 = 120static int factorial(int);
Code:
0: iload_0 // 載入參數 n
1: iconst_1 // 常量 1 入棧
2: if_icmpeq 9 // n == 1 跳轉到 9
5: iload_0 // 載入 n
6: ifne 14 // n != 0 跳轉到 14
9: iload_0 // n 入棧 (n==0 或 n==1)
10: istore_1 // 存入局部變量 1 (factorial)
11: goto 23 // 跳轉返回
14: iload_0 // 載入 n
15: iload_0 // 再次載入 n
16: iconst_1 // 常量 1 入棧
17: isub // n - 1
18: invokestatic #2 // 遞歸調用 factorial(n-1)
21: imul // n * factorial(n-1)
22: istore_1 // 存入結果
23: iload_1 // 載入結果
24: ireturn // 返回
| 指令 | 含義 | 遞歸中的作用 |
|---|---|---|
iload_0 | 從局部變量 0 載入 int 值 | 獲取參數 n |
if_icmpeq | int 比較相等則跳轉 | 基線條件判斷 n==1 |
ifne | 不為零則跳轉 | 基線條件判斷 n!=0 |
isub | int 減法 | 計算 n-1 |
invokestatic | 調用靜態方法 | 遞歸調用自身 |
imul | int 乘法 | 計算 n * 遞歸結果 |
ireturn | 返回 int 值 | 返回計算結果 |
n == 0 || n == 1,避免無限遞歸導致 StackOverflowErrorn * factorial(n-1),問題規模逐步縮小// 迭代實現:O(1) 空間複雜度
public static int factorialIterative(int n) {
int result = 1;
for (int i = 2; i <= n; i++) {
result *= i;
}
return result;
}
總結:遞歸代碼簡潔優雅,但需深理解其堆棧機制。實際開發中應根據問題特性、數據規模、系統資源權衡選擇遞歸或迭代方案。