Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

遞迴

本集目標

讓函數呼叫自己來解決問題,這個技巧叫做「遞迴」。

正文

你有沒有想過:函數可以在自己裡面呼叫自己嗎?

答案是可以的,而且這個技巧叫做遞迴(recursion)。聽起來很玄,但其實概念很簡單。

經典範例:階乘

「5 的階乘」寫成 5!,意思是 5 × 4 × 3 × 2 × 1 = 120

用遞迴的思路想:

  • 5! = 5 × 4!
  • 4! = 4 × 3!
  • 3! = 3 × 2!
  • 2! = 2 × 1!
  • 1! = 1(到這裡停下來)

看到了嗎?每一步都是「自己乘以比自己小一號的階乘」,最後到 1 就停。

fn factorial(n: u32) -> u32 {
    if n <= 1 {
        1
    } else {
        n * factorial(n - 1)
    }
}

fn main() {
    println!("5! = {}", factorial(5));
    println!("3! = {}", factorial(3));
    println!("1! = {}", factorial(1));
}

遞迴的兩個關鍵

每個遞迴函數都需要兩樣東西:

1. base case(基底情況):什麼時候停下來

if n <= 1 {
    1 // 停!不再呼叫自己
}

2. recursive case(遞迴情況):怎麼把問題縮小

n * factorial(n - 1) // 把問題縮小:n 變成 n - 1

如果忘記寫 base case,函數就會無限呼叫自己,最後程式就炸了。

追蹤執行過程

讓我們追蹤 factorial(5) 的執行過程:

factorial(5)
= 5 * factorial(4)
= 5 * (4 * factorial(3))
= 5 * (4 * (3 * factorial(2)))
= 5 * (4 * (3 * (2 * factorial(1))))
= 5 * (4 * (3 * (2 * 1)))
= 5 * (4 * (3 * 2))
= 5 * (4 * 6)
= 5 * 24
= 120

就像俄羅斯套娃一樣,一層一層展開,到底之後再一層一層收回來。

另一個例子:倒數

fn countdown(n: u32) {
    if n <= 0 {
        println!("發射!🚀");
        return;
    }
    println!("{}...", n);
    countdown(n - 1);
}

fn main() {
    countdown(5);
}

遞迴 vs 迴圈

其實上面的例子都可以用迴圈寫。那什麼時候用遞迴?什麼時候用迴圈?

  • 簡單的重複 → 迴圈比較直覺
  • 問題本身就是遞迴結構 → 遞迴比較自然

現在先知道遞迴怎麼寫就好,之後遇到適合的場景自然會用到。

重點整理

  • 遞迴就是函數呼叫自己
  • 一定要有 base case(停止條件),不然會無限迴圈
  • 每次呼叫都要讓問題變小,往 base case 靠近