跳轉至
紙質書（簡體中文版）已發行，詳情請見這裡

# 2.2 迭代與遞迴

在演算法中，重複執行某個任務是很常見的，它與複雜度分析息息相關。因此，在介紹時間複雜度和空間複雜度之前，我們先來了解如何在程式中實現重複執行任務，即兩種基本的程式控制結構：迭代、遞迴。

## 2.2.1 迭代

迭代（iteration）是一種重複執行某個任務的控制結構。在迭代中，程式會在滿足一定的條件下重複執行某段程式碼，直到這個條件不再滿足。

### 1. for 迴圈

`for` 迴圈是最常見的迭代形式之一，適合在預先知道迭代次數時使用。

以下函式基於 `for` 迴圈實現了求和 \(1 + 2 + \dots + n\) ，求和結果使用變數 `res` 記錄。需要注意的是，Python 中 `range(a, b)` 對應的區間是“左閉右開”的，對應的走訪範圍為 \(a, a + 1, \dots, b-1\) ：

Python C++Java C#Go Swift JS TS Dart Rust C Kotlin Ruby Zig
iteration.py

    def for_loop(n: int) -> int:
        """for 迴圈"""
        res = 0
        # 迴圈求和 1, 2, ..., n-1, n
        for i in range(1, n + 1):
            res += i
        return res

iteration.cpp

    /* for 迴圈 */
    int forLoop(int n) {
        int res = 0;
        // 迴圈求和 1, 2, ..., n-1, n
        for (int i = 1; i <= n; ++i) {
            res += i;
        }
        return res;
    }

iteration.java

    /* for 迴圈 */
    int forLoop(int n) {
        int res = 0;
        // 迴圈求和 1, 2, ..., n-1, n
        for (int i = 1; i <= n; i++) {
            res += i;
        }
        return res;
    }

iteration.cs

    /* for 迴圈 */
    int ForLoop(int n) {
        int res = 0;
        // 迴圈求和 1, 2, ..., n-1, n
        for (int i = 1; i <= n; i++) {
            res += i;
        }
        return res;
    }

iteration.go

    /* for 迴圈 */
    func forLoop(n int) int {
        res := 0
        // 迴圈求和 1, 2, ..., n-1, n
        for i := 1; i <= n; i++ {
            res += i
        }
        return res
    }

iteration.swift

    /* for 迴圈 */
    func forLoop(n: Int) -> Int {
        var res = 0
        // 迴圈求和 1, 2, ..., n-1, n
        for i in 1 ... n {
            res += i
        }
        return res
    }

iteration.js

    /* for 迴圈 */
    function forLoop(n) {
        let res = 0;
        // 迴圈求和 1, 2, ..., n-1, n
        for (let i = 1; i <= n; i++) {
            res += i;
        }
        return res;
    }

iteration.ts

    /* for 迴圈 */
    function forLoop(n: number): number {
        let res = 0;
        // 迴圈求和 1, 2, ..., n-1, n
        for (let i = 1; i <= n; i++) {
            res += i;
        }
        return res;
    }

iteration.dart

    /* for 迴圈 */
    int forLoop(int n) {
      int res = 0;
      // 迴圈求和 1, 2, ..., n-1, n
      for (int i = 1; i <= n; i++) {
        res += i;
      }
      return res;
    }

iteration.rs

    /* for 迴圈 */
    fn for_loop(n: i32) -> i32 {
        let mut res = 0;
        // 迴圈求和 1, 2, ..., n-1, n
        for i in 1..=n {
            res += i;
        }
        res
    }

iteration.c

    /* for 迴圈 */
    int forLoop(int n) {
        int res = 0;
        // 迴圈求和 1, 2, ..., n-1, n
        for (int i = 1; i <= n; i++) {
            res += i;
        }
        return res;
    }

iteration.kt

    /* for 迴圈 */
    fun forLoop(n: Int): Int {
        var res = 0
        // 迴圈求和 1, 2, ..., n-1, n
        for (i in 1..n) {
            res += i
        }
        return res
    }

iteration.rb

    ### for 迴圈 ###
    def for_loop(n)
      res = 0

      # 迴圈求和 1, 2, ..., n-1, n
      for i in 1..n
        res += i
      end

      res
    end

iteration.zig

    // for 迴圈
    fn forLoop(n: usize) i32 {
        var res: i32 = 0;
        // 迴圈求和 1, 2, ..., n-1, n
        for (1..n+1) |i| {
            res = res + @as(i32, @intCast(i));
        }
        return res;
    }

視覺化執行

全螢幕觀看 >

圖 2-1 是該求和函式的流程框圖。

圖 2-1 求和函式的流程框圖

此求和函式的操作數量與輸入資料大小 \(n\) 成正比，或者說成“線性關係”。實際上，時間複雜度描述的就是這個“線性關係”。相關內容將會在下一節中詳細介紹。

### 2. while 迴圈

與 `for` 迴圈類似，`while` 迴圈也是一種實現迭代的方法。在 `while` 迴圈中，程式每輪都會先檢查條件，如果條件為真，則繼續執行，否則就結束迴圈。

下面我們用 `while` 迴圈來實現求和 \(1 + 2 + \dots + n\) ：

Python C++Java C#Go Swift JS TS Dart Rust C Kotlin Ruby Zig
iteration.py

    def while_loop(n: int) -> int:
        """while 迴圈"""
        res = 0
        i = 1  # 初始化條件變數
        # 迴圈求和 1, 2, ..., n-1, n
        while i <= n:
            res += i
            i += 1  # 更新條件變數
        return res

iteration.cpp

    /* while 迴圈 */
    int whileLoop(int n) {
        int res = 0;
        int i = 1; // 初始化條件變數
        // 迴圈求和 1, 2, ..., n-1, n
        while (i <= n) {
            res += i;
            i++; // 更新條件變數
        }
        return res;
    }

iteration.java

    /* while 迴圈 */
    int whileLoop(int n) {
        int res = 0;
        int i = 1; // 初始化條件變數
        // 迴圈求和 1, 2, ..., n-1, n
        while (i <= n) {
            res += i;
            i++; // 更新條件變數
        }
        return res;
    }

iteration.cs

    /* while 迴圈 */
    int WhileLoop(int n) {
        int res = 0;
        int i = 1; // 初始化條件變數
        // 迴圈求和 1, 2, ..., n-1, n
        while (i <= n) {
            res += i;
            i += 1; // 更新條件變數
        }
        return res;
    }

iteration.go

    /* while 迴圈 */
    func whileLoop(n int) int {
        res := 0
        // 初始化條件變數
        i := 1
        // 迴圈求和 1, 2, ..., n-1, n
        for i <= n {
            res += i
            // 更新條件變數
            i++
        }
        return res
    }

iteration.swift

    /* while 迴圈 */
    func whileLoop(n: Int) -> Int {
        var res = 0
        var i = 1 // 初始化條件變數
        // 迴圈求和 1, 2, ..., n-1, n
        while i <= n {
            res += i
            i += 1 // 更新條件變數
        }
        return res
    }

iteration.js

    /* while 迴圈 */
    function whileLoop(n) {
        let res = 0;
        let i = 1; // 初始化條件變數
        // 迴圈求和 1, 2, ..., n-1, n
        while (i <= n) {
            res += i;
            i++; // 更新條件變數
        }
        return res;
    }

iteration.ts

    /* while 迴圈 */
    function whileLoop(n: number): number {
        let res = 0;
        let i = 1; // 初始化條件變數
        // 迴圈求和 1, 2, ..., n-1, n
        while (i <= n) {
            res += i;
            i++; // 更新條件變數
        }
        return res;
    }

iteration.dart

    /* while 迴圈 */
    int whileLoop(int n) {
      int res = 0;
      int i = 1; // 初始化條件變數
      // 迴圈求和 1, 2, ..., n-1, n
      while (i <= n) {
        res += i;
        i++; // 更新條件變數
      }
      return res;
    }

iteration.rs

    /* while 迴圈 */
    fn while_loop(n: i32) -> i32 {
        let mut res = 0;
        let mut i = 1; // 初始化條件變數

        // 迴圈求和 1, 2, ..., n-1, n
        while i <= n {
            res += i;
            i += 1; // 更新條件變數
        }
        res
    }

iteration.c

    /* while 迴圈 */
    int whileLoop(int n) {
        int res = 0;
        int i = 1; // 初始化條件變數
        // 迴圈求和 1, 2, ..., n-1, n
        while (i <= n) {
            res += i;
            i++; // 更新條件變數
        }
        return res;
    }

iteration.kt

    /* while 迴圈 */
    fun whileLoop(n: Int): Int {
        var res = 0
        var i = 1 // 初始化條件變數
        // 迴圈求和 1, 2, ..., n-1, n
        while (i <= n) {
            res += i
            i++ // 更新條件變數
        }
        return res
    }

iteration.rb

    ### while 迴圈 ###
    def while_loop(n)
      res = 0
      i = 1 # 初始化條件變數

      # 迴圈求和 1, 2, ..., n-1, n
      while i <= n
        res += i
        i += 1 # 更新條件變數
      end

      res
    end

iteration.zig

    // while 迴圈
    fn whileLoop(n: i32) i32 {
        var res: i32 = 0;
        var i: i32 = 1; // 初始化條件變數
        // 迴圈求和 1, 2, ..., n-1, n
        while (i <= n) {
            res += @intCast(i);
            i += 1;
        }
        return res;
    }

視覺化執行

全螢幕觀看 >

`while` 迴圈比 `for` 迴圈的自由度更高。在 `while` 迴圈中，我們可以自由地設計條件變數的初始化和更新步驟。

例如在以下程式碼中，條件變數 \(i\) 每輪進行兩次更新，這種情況就不太方便用 `for` 迴圈實現：

Python C++Java C#Go Swift JS TS Dart Rust C Kotlin Ruby Zig
iteration.py

    def while_loop_ii(n: int) -> int:
        """while 迴圈（兩次更新）"""
        res = 0
        i = 1  # 初始化條件變數
        # 迴圈求和 1, 4, 10, ...
        while i <= n:
            res += i
            # 更新條件變數
            i += 1
            i *= 2
        return res

iteration.cpp

    /* while 迴圈（兩次更新） */
    int whileLoopII(int n) {
        int res = 0;
        int i = 1; // 初始化條件變數
        // 迴圈求和 1, 4, 10, ...
        while (i <= n) {
            res += i;
            // 更新條件變數
            i++;
            i *= 2;
        }
        return res;
    }

iteration.java

    /* while 迴圈（兩次更新） */
    int whileLoopII(int n) {
        int res = 0;
        int i = 1; // 初始化條件變數
        // 迴圈求和 1, 4, 10, ...
        while (i <= n) {
            res += i;
            // 更新條件變數
            i++;
            i *= 2;
        }
        return res;
    }

iteration.cs

    /* while 迴圈（兩次更新） */
    int WhileLoopII(int n) {
        int res = 0;
        int i = 1; // 初始化條件變數
        // 迴圈求和 1, 4, 10, ...
        while (i <= n) {
            res += i;
            // 更新條件變數
            i += 1;
            i *= 2;
        }
        return res;
    }

iteration.go

    /* while 迴圈（兩次更新） */
    func whileLoopII(n int) int {
        res := 0
        // 初始化條件變數
        i := 1
        // 迴圈求和 1, 4, 10, ...
        for i <= n {
            res += i
            // 更新條件變數
            i++
            i *= 2
        }
        return res
    }

iteration.swift

    /* while 迴圈（兩次更新） */
    func whileLoopII(n: Int) -> Int {
        var res = 0
        var i = 1 // 初始化條件變數
        // 迴圈求和 1, 4, 10, ...
        while i <= n {
            res += i
            // 更新條件變數
            i += 1
            i *= 2
        }
        return res
    }

iteration.js

    /* while 迴圈（兩次更新） */
    function whileLoopII(n) {
        let res = 0;
        let i = 1; // 初始化條件變數
        // 迴圈求和 1, 4, 10, ...
        while (i <= n) {
            res += i;
            // 更新條件變數
            i++;
            i *= 2;
        }
        return res;
    }

iteration.ts

    /* while 迴圈（兩次更新） */
    function whileLoopII(n: number): number {
        let res = 0;
        let i = 1; // 初始化條件變數
        // 迴圈求和 1, 4, 10, ...
        while (i <= n) {
            res += i;
            // 更新條件變數
            i++;
            i *= 2;
        }
        return res;
    }

iteration.dart

    /* while 迴圈（兩次更新） */
    int whileLoopII(int n) {
      int res = 0;
      int i = 1; // 初始化條件變數
      // 迴圈求和 1, 4, 10, ...
      while (i <= n) {
        res += i;
        // 更新條件變數
        i++;
        i *= 2;
      }
      return res;
    }

iteration.rs

    /* while 迴圈（兩次更新） */
    fn while_loop_ii(n: i32) -> i32 {
        let mut res = 0;
        let mut i = 1; // 初始化條件變數

        // 迴圈求和 1, 4, 10, ...
        while i <= n {
            res += i;
            // 更新條件變數
            i += 1;
            i *= 2;
        }
        res
    }

iteration.c

    /* while 迴圈（兩次更新） */
    int whileLoopII(int n) {
        int res = 0;
        int i = 1; // 初始化條件變數
        // 迴圈求和 1, 4, 10, ...
        while (i <= n) {
            res += i;
            // 更新條件變數
            i++;
            i *= 2;
        }
        return res;
    }

iteration.kt

    /* while 迴圈（兩次更新） */
    fun whileLoopII(n: Int): Int {
        var res = 0
        var i = 1 // 初始化條件變數
        // 迴圈求和 1, 4, 10, ...
        while (i <= n) {
            res += i
            // 更新條件變數
            i++
            i *= 2
        }
        return res
    }

iteration.rb

    ### while 迴圈（兩次更新）###
    def while_loop_ii(n)
      res = 0
      i = 1 # 初始化條件變數

      # 迴圈求和 1, 4, 10, ...
      while i <= n
        res += i
        # 更新條件變數
        i += 1
        i *= 2
      end

      res
    end

iteration.zig

    //  while 迴圈（兩次更新）
    fn whileLoopII(n: i32) i32 {
        var res: i32 = 0;
        var i: i32 = 1; // 初始化條件變數
        // 迴圈求和 1, 4, 10, ...
        while (i <= n) {
            res += @intCast(i);
            // 更新條件變數
            i += 1;
            i *= 2;
        }
        return res;
    }

視覺化執行

全螢幕觀看 >

總的來說，`for` 迴圈的程式碼更加緊湊，`while` 迴圈更加靈活，兩者都可以實現迭代結構。選擇使用哪一個應該根據特定問題的需求來決定。

### 3. 巢狀迴圈

我們可以在一個迴圈結構內巢狀另一個迴圈結構，下面以 `for` 迴圈為例：

Python C++Java C#Go Swift JS TS Dart Rust C Kotlin Ruby Zig
iteration.py

    def nested_for_loop(n: int) -> str:
        """雙層 for 迴圈"""
        res = ""
        # 迴圈 i = 1, 2, ..., n-1, n
        for i in range(1, n + 1):
            # 迴圈 j = 1, 2, ..., n-1, n
            for j in range(1, n + 1):
                res += f"({i}, {j}), "
        return res

iteration.cpp

    /* 雙層 for 迴圈 */
    string nestedForLoop(int n) {
        ostringstream res;
        // 迴圈 i = 1, 2, ..., n-1, n
        for (int i = 1; i <= n; ++i) {
            // 迴圈 j = 1, 2, ..., n-1, n
            for (int j = 1; j <= n; ++j) {
                res << "(" << i << ", " << j << "), ";
            }
        }
        return res.str();
    }

iteration.java

    /* 雙層 for 迴圈 */
    String nestedForLoop(int n) {
        StringBuilder res = new StringBuilder();
        // 迴圈 i = 1, 2, ..., n-1, n
        for (int i = 1; i <= n; i++) {
            // 迴圈 j = 1, 2, ..., n-1, n
            for (int j = 1; j <= n; j++) {
                res.append("(" + i + ", " + j + "), ");
            }
        }
        return res.toString();
    }

iteration.cs

    /* 雙層 for 迴圈 */
    string NestedForLoop(int n) {
        StringBuilder res = new();
        // 迴圈 i = 1, 2, ..., n-1, n
        for (int i = 1; i <= n; i++) {
            // 迴圈 j = 1, 2, ..., n-1, n
            for (int j = 1; j <= n; j++) {
                res.Append($"({i}, {j}), ");
            }
        }
        return res.ToString();
    }

iteration.go

    /* 雙層 for 迴圈 */
    func nestedForLoop(n int) string {
        res := ""
        // 迴圈 i = 1, 2, ..., n-1, n
        for i := 1; i <= n; i++ {
            for j := 1; j <= n; j++ {
                // 迴圈 j = 1, 2, ..., n-1, n
                res += fmt.Sprintf("(%d, %d), ", i, j)
            }
        }
        return res
    }

iteration.swift

    /* 雙層 for 迴圈 */
    func nestedForLoop(n: Int) -> String {
        var res = ""
        // 迴圈 i = 1, 2, ..., n-1, n
        for i in 1 ... n {
            // 迴圈 j = 1, 2, ..., n-1, n
            for j in 1 ... n {
                res.append("(\(i), \(j)), ")
            }
        }
        return res
    }

iteration.js

    /* 雙層 for 迴圈 */
    function nestedForLoop(n) {
        let res = '';
        // 迴圈 i = 1, 2, ..., n-1, n
        for (let i = 1; i <= n; i++) {
            // 迴圈 j = 1, 2, ..., n-1, n
            for (let j = 1; j <= n; j++) {
                res += `(${i}, ${j}), `;
            }
        }
        return res;
    }

iteration.ts

    /* 雙層 for 迴圈 */
    function nestedForLoop(n: number): string {
        let res = '';
        // 迴圈 i = 1, 2, ..., n-1, n
        for (let i = 1; i <= n; i++) {
            // 迴圈 j = 1, 2, ..., n-1, n
            for (let j = 1; j <= n; j++) {
                res += `(${i}, ${j}), `;
            }
        }
        return res;
    }

iteration.dart

    /* 雙層 for 迴圈 */
    String nestedForLoop(int n) {
      String res = "";
      // 迴圈 i = 1, 2, ..., n-1, n
      for (int i = 1; i <= n; i++) {
        // 迴圈 j = 1, 2, ..., n-1, n
        for (int j = 1; j <= n; j++) {
          res += "($i, $j), ";
        }
      }
      return res;
    }

iteration.rs

    /* 雙層 for 迴圈 */
    fn nested_for_loop(n: i32) -> String {
        let mut res = vec![];
        // 迴圈 i = 1, 2, ..., n-1, n
        for i in 1..=n {
            // 迴圈 j = 1, 2, ..., n-1, n
            for j in 1..=n {
                res.push(format!("({}, {}), ", i, j));
            }
        }
        res.join("")
    }

iteration.c

    /* 雙層 for 迴圈 */
    char *nestedForLoop(int n) {
        // n * n 為對應點數量，"(i, j), " 對應字串長最大為 6+10*2，加上最後一個空字元 \0 的額外空間
        int size = n * n * 26 + 1;
        char *res = malloc(size * sizeof(char));
        // 迴圈 i = 1, 2, ..., n-1, n
        for (int i = 1; i <= n; i++) {
            // 迴圈 j = 1, 2, ..., n-1, n
            for (int j = 1; j <= n; j++) {
                char tmp[26];
                snprintf(tmp, sizeof(tmp), "(%d, %d), ", i, j);
                strncat(res, tmp, size - strlen(res) - 1);
            }
        }
        return res;
    }

iteration.kt

    /* 雙層 for 迴圈 */
    fun nestedForLoop(n: Int): String {
        val res = StringBuilder()
        // 迴圈 i = 1, 2, ..., n-1, n
        for (i in 1..n) {
            // 迴圈 j = 1, 2, ..., n-1, n
            for (j in 1..n) {
                res.append(" ($i, $j), ")
            }
        }
        return res.toString()
    }

iteration.rb

    ### 雙層 for 迴圈 ###
    def nested_for_loop(n)
      res = ""

      # 迴圈 i = 1, 2, ..., n-1, n
      for i in 1..n
        # 迴圈 j = 1, 2, ..., n-1, n
        for j in 1..n
          res += "(#{i}, #{j}), "
        end
      end

      res
    end

iteration.zig

    // 雙層 for 迴圈
    fn nestedForLoop(allocator: Allocator, n: usize) ![]const u8 {
        var res = std.ArrayList(u8).init(allocator);
        defer res.deinit();
        var buffer: [20]u8 = undefined;
        // 迴圈 i = 1, 2, ..., n-1, n
        for (1..n+1) |i| {
            // 迴圈 j = 1, 2, ..., n-1, n
            for (1..n+1) |j| {
                var _str = try std.fmt.bufPrint(&buffer, "({d}, {d}), ", .{i, j});
                try res.appendSlice(_str);
            }
        }
        return res.toOwnedSlice();
    }

視覺化執行

全螢幕觀看 >

圖 2-2 是該巢狀迴圈的流程框圖。

圖 2-2 巢狀迴圈的流程框圖

在這種情況下，函式的操作數量與 \(n^2\) 成正比，或者說演算法執行時間和輸入資料大小 \(n\) 成“平方關係”。

我們可以繼續新增巢狀迴圈，每一次巢狀都是一次“升維”，將會使時間複雜度提高至“立方關係”“四次方關係”，以此類推。

## 2.2.2 遞迴

遞迴（recursion）是一種演算法策略，透過函式呼叫自身來解決問題。它主要包含兩個階段。

1. 遞：程式不斷深入地呼叫自身，通常傳入更小或更簡化的參數，直到達到“終止條件”。
2. 迴：觸發“終止條件”後，程式從最深層的遞迴函式開始逐層返回，匯聚每一層的結果。

而從實現的角度看，遞迴程式碼主要包含三個要素。

1. 終止條件：用於決定什麼時候由“遞”轉“迴”。
2. 遞迴呼叫：對應“遞”，函式呼叫自身，通常輸入更小或更簡化的參數。
3. 返回結果：對應“迴”，將當前遞迴層級的結果返回至上一層。

觀察以下程式碼，我們只需呼叫函式 `recur(n)` ，就可以完成 \(1 + 2 + \dots + n\) 的計算：

Python C++Java C#Go Swift JS TS Dart Rust C Kotlin Ruby Zig
recursion.py

    def recur(n: int) -> int:
        """遞迴"""
        # 終止條件
        if n == 1:
            return 1
        # 遞：遞迴呼叫
        res = recur(n - 1)
        # 迴：返回結果
        return n + res

recursion.cpp

    /* 遞迴 */
    int recur(int n) {
        // 終止條件
        if (n == 1)
            return 1;
        // 遞：遞迴呼叫
        int res = recur(n - 1);
        // 迴：返回結果
        return n + res;
    }

recursion.java

    /* 遞迴 */
    int recur(int n) {
        // 終止條件
        if (n == 1)
            return 1;
        // 遞：遞迴呼叫
        int res = recur(n - 1);
        // 迴：返回結果
        return n + res;
    }

recursion.cs

    /* 遞迴 */
    int Recur(int n) {
        // 終止條件
        if (n == 1)
            return 1;
        // 遞：遞迴呼叫
        int res = Recur(n - 1);
        // 迴：返回結果
        return n + res;
    }

recursion.go

    /* 遞迴 */
    func recur(n int) int {
        // 終止條件
        if n == 1 {
            return 1
        }
        // 遞：遞迴呼叫
        res := recur(n - 1)
        // 迴：返回結果
        return n + res
    }

recursion.swift

    /* 遞迴 */
    func recur(n: Int) -> Int {
        // 終止條件
        if n == 1 {
            return 1
        }
        // 遞：遞迴呼叫
        let res = recur(n: n - 1)
        // 迴：返回結果
        return n + res
    }

recursion.js

    /* 遞迴 */
    function recur(n) {
        // 終止條件
        if (n === 1) return 1;
        // 遞：遞迴呼叫
        const res = recur(n - 1);
        // 迴：返回結果
        return n + res;
    }

recursion.ts

    /* 遞迴 */
    function recur(n: number): number {
        // 終止條件
        if (n === 1) return 1;
        // 遞：遞迴呼叫
        const res = recur(n - 1);
        // 迴：返回結果
        return n + res;
    }

recursion.dart

    /* 遞迴 */
    int recur(int n) {
      // 終止條件
      if (n == 1) return 1;
      // 遞：遞迴呼叫
      int res = recur(n - 1);
      // 迴：返回結果
      return n + res;
    }

recursion.rs

    /* 遞迴 */
    fn recur(n: i32) -> i32 {
        // 終止條件
        if n == 1 {
            return 1;
        }
        // 遞：遞迴呼叫
        let res = recur(n - 1);
        // 迴：返回結果
        n + res
    }

recursion.c

    /* 遞迴 */
    int recur(int n) {
        // 終止條件
        if (n == 1)
            return 1;
        // 遞：遞迴呼叫
        int res = recur(n - 1);
        // 迴：返回結果
        return n + res;
    }

recursion.kt

    /* 遞迴 */
    fun recur(n: Int): Int {
        // 終止條件
        if (n == 1)
            return 1
        // 遞: 遞迴呼叫
        val res = recur(n - 1)
        // 迴: 返回結果
        return n + res
    }

recursion.rb

    ### 遞迴 ###
    def recur(n)
      # 終止條件
      return 1 if n == 1
      # 遞：遞迴呼叫
      res = recur(n - 1)
      # 迴：返回結果
      n + res
    end

recursion.zig

    // 遞迴函式
    fn recur(n: i32) i32 {
        // 終止條件
        if (n == 1) {
            return 1;
        }
        // 遞：遞迴呼叫
        var res: i32 = recur(n - 1);
        // 迴：返回結果
        return n + res;
    }

視覺化執行

全螢幕觀看 >

圖 2-3 展示了該函式的遞迴過程。

圖 2-3 求和函式的遞迴過程

雖然從計算角度看，迭代與遞迴可以得到相同的結果，但它們代表了兩種完全不同的思考和解決問題的範式。

- 迭代：“自下而上”地解決問題。從最基礎的步驟開始，然後不斷重複或累加這些步驟，直到任務完成。
- 遞迴：“自上而下”地解決問題。將原問題分解為更小的子問題，這些子問題和原問題具有相同的形式。接下來將子問題繼續分解為更小的子問題，直到基本情況時停止（基本情況的解是已知的）。

以上述求和函式為例，設問題 \(f(n) = 1 + 2 + \dots + n\) 。

- 迭代：在迴圈中模擬求和過程，從 \(1\) 走訪到 \(n\) ，每輪執行求和操作，即可求得 \(f(n)\) 。
- 遞迴：將問題分解為子問題 \(f(n) = n + f(n-1)\) ，不斷（遞迴地）分解下去，直至基本情況 \(f(1) = 1\) 時終止。

### 1. 呼叫堆疊

遞迴函式每次呼叫自身時，系統都會為新開啟的函式分配記憶體，以儲存區域性變數、呼叫位址和其他資訊等。這將導致兩方面的結果。

- 函式的上下文資料都儲存在稱為“堆疊幀空間”的記憶體區域中，直至函式返回後才會被釋放。因此，遞迴通常比迭代更加耗費記憶體空間。
- 遞迴呼叫函式會產生額外的開銷。因此遞迴通常比迴圈的時間效率更低。

如圖 2-4 所示，在觸發終止條件前，同時存在 \(n\) 個未返回的遞迴函式，遞迴深度為 \(n\) 。

圖 2-4 遞迴呼叫深度

在實際中，程式語言允許的遞迴深度通常是有限的，過深的遞迴可能導致堆疊溢位錯誤。

### 2. 尾遞迴

有趣的是，如果函式在返回前的最後一步才進行遞迴呼叫，則該函式可以被編譯器或直譯器最佳化，使其在空間效率上與迭代相當。這種情況被稱為尾遞迴（tail recursion）。

- 普通遞迴：當函式返回到上一層級的函式後，需要繼續執行程式碼，因此系統需要儲存上一層呼叫的上下文。
- 尾遞迴：遞迴呼叫是函式返回前的最後一個操作，這意味著函式返回到上一層級後，無須繼續執行其他操作，因此系統無須儲存上一層函式的上下文。

以計算 \(1 + 2 + \dots + n\) 為例，我們可以將結果變數 `res` 設為函式參數，從而實現尾遞迴：

Python C++Java C#Go Swift JS TS Dart Rust C Kotlin Ruby Zig
recursion.py

    def tail_recur(n, res):
        """尾遞迴"""
        # 終止條件
        if n == 0:
            return res
        # 尾遞迴呼叫
        return tail_recur(n - 1, res + n)

recursion.cpp

    /* 尾遞迴 */
    int tailRecur(int n, int res) {
        // 終止條件
        if (n == 0)
            return res;
        // 尾遞迴呼叫
        return tailRecur(n - 1, res + n);
    }

recursion.java

    /* 尾遞迴 */
    int tailRecur(int n, int res) {
        // 終止條件
        if (n == 0)
            return res;
        // 尾遞迴呼叫
        return tailRecur(n - 1, res + n);
    }

recursion.cs

    /* 尾遞迴 */
    int TailRecur(int n, int res) {
        // 終止條件
        if (n == 0)
            return res;
        // 尾遞迴呼叫
        return TailRecur(n - 1, res + n);
    }

recursion.go

    /* 尾遞迴 */
    func tailRecur(n int, res int) int {
        // 終止條件
        if n == 0 {
            return res
        }
        // 尾遞迴呼叫
        return tailRecur(n-1, res+n)
    }

recursion.swift

    /* 尾遞迴 */
    func tailRecur(n: Int, res: Int) -> Int {
        // 終止條件
        if n == 0 {
            return res
        }
        // 尾遞迴呼叫
        return tailRecur(n: n - 1, res: res + n)
    }

recursion.js

    /* 尾遞迴 */
    function tailRecur(n, res) {
        // 終止條件
        if (n === 0) return res;
        // 尾遞迴呼叫
        return tailRecur(n - 1, res + n);
    }

recursion.ts

    /* 尾遞迴 */
    function tailRecur(n: number, res: number): number {
        // 終止條件
        if (n === 0) return res;
        // 尾遞迴呼叫
        return tailRecur(n - 1, res + n);
    }

recursion.dart

    /* 尾遞迴 */
    int tailRecur(int n, int res) {
      // 終止條件
      if (n == 0) return res;
      // 尾遞迴呼叫
      return tailRecur(n - 1, res + n);
    }

recursion.rs

    /* 尾遞迴 */
    fn tail_recur(n: i32, res: i32) -> i32 {
        // 終止條件
        if n == 0 {
            return res;
        }
        // 尾遞迴呼叫
        tail_recur(n - 1, res + n)
    }

recursion.c

    /* 尾遞迴 */
    int tailRecur(int n, int res) {
        // 終止條件
        if (n == 0)
            return res;
        // 尾遞迴呼叫
        return tailRecur(n - 1, res + n);
    }

recursion.kt

    /* 尾遞迴 */
    tailrec fun tailRecur(n: Int, res: Int): Int {
        // 新增 tailrec 關鍵詞，以開啟尾遞迴最佳化
        // 終止條件
        if (n == 0)
            return res
        // 尾遞迴呼叫
        return tailRecur(n - 1, res + n)
    }

recursion.rb

    ### 尾遞迴 ###
    def tail_recur(n, res)
      # 終止條件
      return res if n == 0
      # 尾遞迴呼叫
      tail_recur(n - 1, res + n)
    end

recursion.zig

    // 尾遞迴函式
    fn tailRecur(n: i32, res: i32) i32 {
        // 終止條件
        if (n == 0) {
            return res;
        }
        // 尾遞迴呼叫
        return tailRecur(n - 1, res + n);
    }

視覺化執行

全螢幕觀看 >

尾遞迴的執行過程如圖 2-5 所示。對比普通遞迴和尾遞迴，兩者的求和操作的執行點是不同的。

- 普通遞迴：求和操作是在“迴”的過程中執行的，每層返回後都要再執行一次求和操作。
- 尾遞迴：求和操作是在“遞”的過程中執行的，“迴”的過程只需層層返回。

圖 2-5 尾遞迴過程

Tip

請注意，許多編譯器或直譯器並不支持尾遞迴最佳化。例如，Python 預設不支持尾遞迴最佳化，因此即使函式是尾遞迴形式，仍然可能會遇到堆疊溢位問題。

### 3. 遞迴樹

當處理與“分治”相關的演算法問題時，遞迴往往比迭代的思路更加直觀、程式碼更加易讀。以“費波那契數列”為例。

Question

給定一個費波那契數列 \(0, 1, 1, 2, 3, 5, 8, 13, \dots\) ，求該數列的第 \(n\) 個數字。

設費波那契數列的第 \(n\) 個數字為 \(f(n)\) ，易得兩個結論。

- 數列的前兩個數字為 \(f(1) = 0\) 和 \(f(2) = 1\) 。
- 數列中的每個數字是前兩個數字的和，即 \(f(n) = f(n - 1) + f(n - 2)\) 。

按照遞推關係進行遞迴呼叫，將前兩個數字作為終止條件，便可寫出遞迴程式碼。呼叫 `fib(n)` 即可得到費波那契數列的第 \(n\) 個數字：

Python C++Java C#Go Swift JS TS Dart Rust C Kotlin Ruby Zig
recursion.py

    def fib(n: int) -> int:
        """費波那契數列：遞迴"""
        # 終止條件 f(1) = 0, f(2) = 1
        if n == 1 or n == 2:
            return n - 1
        # 遞迴呼叫 f(n) = f(n-1) + f(n-2)
        res = fib(n - 1) + fib(n - 2)
        # 返回結果 f(n)
        return res

recursion.cpp

    /* 費波那契數列：遞迴 */
    int fib(int n) {
        // 終止條件 f(1) = 0, f(2) = 1
        if (n == 1 || n == 2)
            return n - 1;
        // 遞迴呼叫 f(n) = f(n-1) + f(n-2)
        int res = fib(n - 1) + fib(n - 2);
        // 返回結果 f(n)
        return res;
    }

recursion.java

    /* 費波那契數列：遞迴 */
    int fib(int n) {
        // 終止條件 f(1) = 0, f(2) = 1
        if (n == 1 || n == 2)
            return n - 1;
        // 遞迴呼叫 f(n) = f(n-1) + f(n-2)
        int res = fib(n - 1) + fib(n - 2);
        // 返回結果 f(n)
        return res;
    }

recursion.cs

    /* 費波那契數列：遞迴 */
    int Fib(int n) {
        // 終止條件 f(1) = 0, f(2) = 1
        if (n == 1 || n == 2)
            return n - 1;
        // 遞迴呼叫 f(n) = f(n-1) + f(n-2)
        int res = Fib(n - 1) + Fib(n - 2);
        // 返回結果 f(n)
        return res;
    }

recursion.go

    /* 費波那契數列：遞迴 */
    func fib(n int) int {
        // 終止條件 f(1) = 0, f(2) = 1
        if n == 1 || n == 2 {
            return n - 1
        }
        // 遞迴呼叫 f(n) = f(n-1) + f(n-2)
        res := fib(n-1) + fib(n-2)
        // 返回結果 f(n)
        return res
    }

recursion.swift

    /* 費波那契數列：遞迴 */
    func fib(n: Int) -> Int {
        // 終止條件 f(1) = 0, f(2) = 1
        if n == 1 || n == 2 {
            return n - 1
        }
        // 遞迴呼叫 f(n) = f(n-1) + f(n-2)
        let res = fib(n: n - 1) + fib(n: n - 2)
        // 返回結果 f(n)
        return res
    }

recursion.js

    /* 費波那契數列：遞迴 */
    function fib(n) {
        // 終止條件 f(1) = 0, f(2) = 1
        if (n === 1 || n === 2) return n - 1;
        // 遞迴呼叫 f(n) = f(n-1) + f(n-2)
        const res = fib(n - 1) + fib(n - 2);
        // 返回結果 f(n)
        return res;
    }

recursion.ts

    /* 費波那契數列：遞迴 */
    function fib(n: number): number {
        // 終止條件 f(1) = 0, f(2) = 1
        if (n === 1 || n === 2) return n - 1;
        // 遞迴呼叫 f(n) = f(n-1) + f(n-2)
        const res = fib(n - 1) + fib(n - 2);
        // 返回結果 f(n)
        return res;
    }

recursion.dart

    /* 費波那契數列：遞迴 */
    int fib(int n) {
      // 終止條件 f(1) = 0, f(2) = 1
      if (n == 1 || n == 2) return n - 1;
      // 遞迴呼叫 f(n) = f(n-1) + f(n-2)
      int res = fib(n - 1) + fib(n - 2);
      // 返回結果 f(n)
      return res;
    }

recursion.rs

    /* 費波那契數列：遞迴 */
    fn fib(n: i32) -> i32 {
        // 終止條件 f(1) = 0, f(2) = 1
        if n == 1 || n == 2 {
            return n - 1;
        }
        // 遞迴呼叫 f(n) = f(n-1) + f(n-2)
        let res = fib(n - 1) + fib(n - 2);
        // 返回結果
        res
    }

recursion.c

    /* 費波那契數列：遞迴 */
    int fib(int n) {
        // 終止條件 f(1) = 0, f(2) = 1
        if (n == 1 || n == 2)
            return n - 1;
        // 遞迴呼叫 f(n) = f(n-1) + f(n-2)
        int res = fib(n - 1) + fib(n - 2);
        // 返回結果 f(n)
        return res;
    }

recursion.kt

    /* 費波那契數列：遞迴 */
    fun fib(n: Int): Int {
        // 終止條件 f(1) = 0, f(2) = 1
        if (n == 1 || n == 2)
            return n - 1
        // 遞迴呼叫 f(n) = f(n-1) + f(n-2)
        val res = fib(n - 1) + fib(n - 2)
        // 返回結果 f(n)
        return res
    }

recursion.rb

    ### 費波那契數列：遞迴 ###
    def fib(n)
      # 終止條件 f(1) = 0, f(2) = 1
      return n - 1 if n == 1 || n == 2
      # 遞迴呼叫 f(n) = f(n-1) + f(n-2)
      res = fib(n - 1) + fib(n - 2)
      # 返回結果 f(n)
      res
    end

recursion.zig

    // 費波那契數列
    fn fib(n: i32) i32 {
        // 終止條件 f(1) = 0, f(2) = 1
        if (n == 1 or n == 2) {
            return n - 1;
        }
        // 遞迴呼叫 f(n) = f(n-1) + f(n-2)
        var res: i32 = fib(n - 1) + fib(n - 2);
        // 返回結果 f(n)
        return res;
    }

視覺化執行

全螢幕觀看 >

觀察以上程式碼，我們在函式內遞迴呼叫了兩個函式，這意味著從一個呼叫產生了兩個呼叫分支。如圖 2-6 所示，這樣不斷遞迴呼叫下去，最終將產生一棵層數為 \(n\) 的遞迴樹（recursion tree）。

圖 2-6 費波那契數列的遞迴樹

從本質上看，遞迴體現了“將問題分解為更小子問題”的思維範式，這種分治策略至關重要。

- 從演算法角度看，搜尋、排序、回溯、分治、動態規劃等許多重要演算法策略直接或間接地應用了這種思維方式。
- 從資料結構角度看，遞迴天然適合處理鏈結串列、樹和圖的相關問題，因為它們非常適合用分治思想進行分析。

## 2.2.3 兩者對比

總結以上內容，如表 2-1 所示，迭代和遞迴在實現、效能和適用性上有所不同。

表 2-1 迭代與遞迴特點對比

| 迭代 | 遞迴
--- | --- | ---
實現方式 | 迴圈結構 | 函式呼叫自身
時間效率 | 效率通常較高，無函式呼叫開銷 | 每次函式呼叫都會產生開銷
記憶體使用 | 通常使用固定大小的記憶體空間 | 累積函式呼叫可能使用大量的堆疊幀空間
適用問題 | 適用於簡單迴圈任務，程式碼直觀、可讀性好 | 適用於子問題分解，如樹、圖、分治、回溯等，程式碼結構簡潔、清晰

Tip

如果感覺以下內容理解困難，可以在讀完“堆疊”章節後再來複習。

那麼，迭代和遞迴具有什麼內在關聯呢？以上述遞迴函式為例，求和操作在遞迴的“迴”階段進行。這意味著最初被呼叫的函式實際上是最後完成其求和操作的，這種工作機制與堆疊的“先入後出”原則異曲同工。

事實上，“呼叫堆疊”和“堆疊幀空間”這類遞迴術語已經暗示了遞迴與堆疊之間的密切關係。

1. 遞：當函式被呼叫時，系統會在“呼叫堆疊”上為該函式分配新的堆疊幀，用於儲存函式的區域性變數、參數、返回位址等資料。
2. 迴：當函式完成執行並返回時，對應的堆疊幀會被從“呼叫堆疊”上移除，恢復之前函式的執行環境。

因此，我們可以使用一個顯式的堆疊來模擬呼叫堆疊的行為，從而將遞迴轉化為迭代形式：

Python C++Java C#Go Swift JS TS Dart Rust C Kotlin Ruby Zig
recursion.py

    def for_loop_recur(n: int) -> int:
        """使用迭代模擬遞迴"""
        # 使用一個顯式的堆疊來模擬系統呼叫堆疊
        stack = []
        res = 0
        # 遞：遞迴呼叫
        for i in range(n, 0, -1):
            # 透過“入堆疊操作”模擬“遞”
            stack.append(i)
        # 迴：返回結果
        while stack:
            # 透過“出堆疊操作”模擬“迴”
            res += stack.pop()
        # res = 1+2+3+...+n
        return res

recursion.cpp

    /* 使用迭代模擬遞迴 */
    int forLoopRecur(int n) {
        // 使用一個顯式的堆疊來模擬系統呼叫堆疊
        stack<int> stack;
        int res = 0;
        // 遞：遞迴呼叫
        for (int i = n; i > 0; i--) {
            // 透過“入堆疊操作”模擬“遞”
            stack.push(i);
        }
        // 迴：返回結果
        while (!stack.empty()) {
            // 透過“出堆疊操作”模擬“迴”
            res += stack.top();
            stack.pop();
        }
        // res = 1+2+3+...+n
        return res;
    }

recursion.java

    /* 使用迭代模擬遞迴 */
    int forLoopRecur(int n) {
        // 使用一個顯式的堆疊來模擬系統呼叫堆疊
        Stack<Integer> stack = new Stack<>();
        int res = 0;
        // 遞：遞迴呼叫
        for (int i = n; i > 0; i--) {
            // 透過“入堆疊操作”模擬“遞”
            stack.push(i);
        }
        // 迴：返回結果
        while (!stack.isEmpty()) {
            // 透過“出堆疊操作”模擬“迴”
            res += stack.pop();
        }
        // res = 1+2+3+...+n
        return res;
    }

recursion.cs

    /* 使用迭代模擬遞迴 */
    int ForLoopRecur(int n) {
        // 使用一個顯式的堆疊來模擬系統呼叫堆疊
        Stack<int> stack = new();
        int res = 0;
        // 遞：遞迴呼叫
        for (int i = n; i > 0; i--) {
            // 透過“入堆疊操作”模擬“遞”
            stack.Push(i);
        }
        // 迴：返回結果
        while (stack.Count > 0) {
            // 透過“出堆疊操作”模擬“迴”
            res += stack.Pop();
        }
        // res = 1+2+3+...+n
        return res;
    }

recursion.go

    /* 使用迭代模擬遞迴 */
    func forLoopRecur(n int) int {
        // 使用一個顯式的堆疊來模擬系統呼叫堆疊
        stack := list.New()
        res := 0
        // 遞：遞迴呼叫
        for i := n; i > 0; i-- {
            // 透過“入堆疊操作”模擬“遞”
            stack.PushBack(i)
        }
        // 迴：返回結果
        for stack.Len() != 0 {
            // 透過“出堆疊操作”模擬“迴”
            res += stack.Back().Value.(int)
            stack.Remove(stack.Back())
        }
        // res = 1+2+3+...+n
        return res
    }

recursion.swift

    /* 使用迭代模擬遞迴 */
    func forLoopRecur(n: Int) -> Int {
        // 使用一個顯式的堆疊來模擬系統呼叫堆疊
        var stack: [Int] = []
        var res = 0
        // 遞：遞迴呼叫
        for i in (1 ... n).reversed() {
            // 透過“入堆疊操作”模擬“遞”
            stack.append(i)
        }
        // 迴：返回結果
        while !stack.isEmpty {
            // 透過“出堆疊操作”模擬“迴”
            res += stack.removeLast()
        }
        // res = 1+2+3+...+n
        return res
    }

recursion.js

    /* 使用迭代模擬遞迴 */
    function forLoopRecur(n) {
        // 使用一個顯式的堆疊來模擬系統呼叫堆疊
        const stack = [];
        let res = 0;
        // 遞：遞迴呼叫
        for (let i = n; i > 0; i--) {
            // 透過“入堆疊操作”模擬“遞”
            stack.push(i);
        }
        // 迴：返回結果
        while (stack.length) {
            // 透過“出堆疊操作”模擬“迴”
            res += stack.pop();
        }
        // res = 1+2+3+...+n
        return res;
    }

recursion.ts

    /* 使用迭代模擬遞迴 */
    function forLoopRecur(n: number): number {
        // 使用一個顯式的堆疊來模擬系統呼叫堆疊
        const stack: number[] = [];
        let res: number = 0;
        // 遞：遞迴呼叫
        for (let i = n; i > 0; i--) {
            // 透過“入堆疊操作”模擬“遞”
            stack.push(i);
        }
        // 迴：返回結果
        while (stack.length) {
            // 透過“出堆疊操作”模擬“迴”
            res += stack.pop();
        }
        // res = 1+2+3+...+n
        return res;
    }

recursion.dart

    /* 使用迭代模擬遞迴 */
    int forLoopRecur(int n) {
      // 使用一個顯式的堆疊來模擬系統呼叫堆疊
      List<int> stack = [];
      int res = 0;
      // 遞：遞迴呼叫
      for (int i = n; i > 0; i--) {
        // 透過“入堆疊操作”模擬“遞”
        stack.add(i);
      }
      // 迴：返回結果
      while (!stack.isEmpty) {
        // 透過“出堆疊操作”模擬“迴”
        res += stack.removeLast();
      }
      // res = 1+2+3+...+n
      return res;
    }

recursion.rs

    /* 使用迭代模擬遞迴 */
    fn for_loop_recur(n: i32) -> i32 {
        // 使用一個顯式的堆疊來模擬系統呼叫堆疊
        let mut stack = Vec::new();
        let mut res = 0;
        // 遞：遞迴呼叫
        for i in (1..=n).rev() {
            // 透過“入堆疊操作”模擬“遞”
            stack.push(i);
        }
        // 迴：返回結果
        while !stack.is_empty() {
            // 透過“出堆疊操作”模擬“迴”
            res += stack.pop().unwrap();
        }
        // res = 1+2+3+...+n
        res
    }

recursion.c

    /* 使用迭代模擬遞迴 */
    int forLoopRecur(int n) {
        int stack[1000]; // 藉助一個大陣列來模擬堆疊
        int top = -1;    // 堆疊頂索引
        int res = 0;
        // 遞：遞迴呼叫
        for (int i = n; i > 0; i--) {
            // 透過“入堆疊操作”模擬“遞”
            stack[1 + top++] = i;
        }
        // 迴：返回結果
        while (top >= 0) {
            // 透過“出堆疊操作”模擬“迴”
            res += stack[top--];
        }
        // res = 1+2+3+...+n
        return res;
    }

recursion.kt

    /* 使用迭代模擬遞迴 */
    fun forLoopRecur(n: Int): Int {
        // 使用一個顯式的堆疊來模擬系統呼叫堆疊
        val stack = Stack<Int>()
        var res = 0
        // 遞: 遞迴呼叫
        for (i in n downTo 0) {
            // 透過“入堆疊操作”模擬“遞”
            stack.push(i)
        }
        // 迴: 返回結果
        while (stack.isNotEmpty()) {
            // 透過“出堆疊操作”模擬“迴”
            res += stack.pop()
        }
        // res = 1+2+3+...+n
        return res
    }

recursion.rb

    ### 使用迭代模擬遞迴 ###
    def for_loop_recur(n)
      # 使用一個顯式的堆疊來模擬系統呼叫堆疊
      stack = []
      res = 0

      # 遞：遞迴呼叫
      for i in n.downto(0)
        # 透過“入堆疊操作”模擬“遞”
        stack << i
      end
      # 迴：返回結果
      while !stack.empty?
        res += stack.pop
      end

      # res = 1+2+3+...+n
      res
    end

recursion.zig

    // 使用迭代模擬遞迴
    fn forLoopRecur(comptime n: i32) i32 {
        // 使用一個顯式的堆疊來模擬系統呼叫堆疊
        var stack: [n]i32 = undefined;
        var res: i32 = 0;
        // 遞：遞迴呼叫
        var i: usize = n;
        while (i > 0) {
            stack[i - 1] = @intCast(i);
            i -= 1;
        }
        // 迴：返回結果
        var index: usize = n;
        while (index > 0) {
            index -= 1;
            res += stack[index];
        }
        // res = 1+2+3+...+n
        return res;
    }

視覺化執行

全螢幕觀看 >

觀察以上程式碼，當遞迴轉化為迭代後，程式碼變得更加複雜了。儘管迭代和遞迴在很多情況下可以互相轉化，但不一定值得這樣做，有以下兩點原因。

- 轉化後的程式碼可能更加難以理解，可讀性更差。
- 對於某些複雜問題，模擬系統呼叫堆疊的行為可能非常困難。

總之，選擇迭代還是遞迴取決於特定問題的性質。在程式設計實踐中，權衡兩者的優劣並根據情境選擇合適的方法至關重要。

#####