iT邦幫忙

2022 iThome 鐵人賽

DAY 18
0
Software Development

程式基礎概念討論系列 第 18

[DAY 18] 自己呼叫自己的遞迴函式

  • 分享至 

  • xImage
  •  

今天,我們要討論的是函式的一種較進階的應用方式,遞迴函式 (Recursive Function)。遞迴函式指的是當我們在執行某個函式時,在函式完結前重新呼叫該函式,也就是說讓函式呼叫自己

使用遞迴函式時第一項需要注意的是它的執行順序,程式的運作邏輯是優先執行最後被呼叫的函式,當在函式中呼叫函式時,原來的函式會暫時執行,直到被呼叫的函式執行結束後,才會繼續執行原來的函式內容。因此,我們在使用遞迴函式時,最後被呼叫的那一次反而是最先被執行的。

而第二項需要注意的是在函式中必須要包含能跳出遞迴的部分,遞迴函式的概念是讓函式呼叫自身來重複執行某些特定的動作,從這一點來看,我們能知道遞迴函式很明顯是一種迴圈的變體。因此,我們必須要小心的就是每一種迴圈都會面對的惡夢,無限迴圈。所以當我們在使用遞迴函式時,需要有可以讓迴圈停止的方法,一般來說,會透過回傳陳述式跟 if-else 陳述式來進行控制

現在就讓我們以經典的階乘 (Factorial,!) 來作例子:

階乘是一種數學的計算方式,指的是所有小於及等於該數的正整數之積,舉例來說,當我們要進行 5 的階乘時:
5! = 5x4! = 5x4x3! = 5x4x3x2! = 5x4x3x2x1! = 5x4x3x2x1 = 120
而從上面的算式來看,我們會發現階乘的概念就是不停的把數字乘上自己的值減一的階乘,直到值變為一為止,這正好可以做成一個遞迴函式:[Python]

def factorial(n):
    if n > 1:
        return n * factorial(n - 1) # 當 n 大於 1 時,呼叫自己並以 n - 1 作為參數,然後回傳呼叫函式回傳的結果 * n
    else:
        return 1 # 當 n 小於或等於 1 時,跳出遞迴,直接回傳 1

print(factorial(5))

當我們執行程式時,程式的執行順序為:

1. 呼叫 factorial(5)
2. 由於 n 為 5 [大於 1],因此回傳 5 * factorial(4)
3. 由於呼叫了 factorial(4),因此停止執行 factorial(5),優先執行 factorial(4)
4. 由於 n 為 4 [大於 1],因此回傳 4 * factorial(3)
5. 重複上述的行為,直至 n 為 1 時,factorial(1) 直接回傳 1
6. factorial(2) 接收到 factorial(1) 的回傳結果 [1],因此回傳 2 * 1 的結果 [2]
7. 如此類推,最終 factorial(5) 回傳 5 * 24 的結果 [120]

執行 factorial(5) -> 5 * factorial(4)
-> 執行 factorial(4) -> 4 * factorial(3)
-> 執行 factorial(3) -> 3 * factorial(2)
-> 執行 factorial(2) -> 2 * factorial(1)
-> 執行 factorial(1) -> *回傳 1 [停止遞迴的進行]*
-> 繼續執行 factorial(2) -> 回傳 2 * 1 [2]
-> 繼續執行 factorial(3) -> 回傳 3 * 2 [6]
-> 繼續執行 factorial(4) -> 回傳 4 * 6 [24]
-> 繼續執行 factorial(5) -> 回傳 5 * 24 [120]
=> 最終得到回傳結果 120

不過,可能大家會發現,其實我們也可以使用其他的迴圈陳述式來做到同樣的事,舉例來說,我們可以使用 for 迴圈陳述式:[C#]

using System;

public class FactorialExample
{
    public static int factorial(int n) {
        int result = 1;
        for (int i = n; i > 0; i--) {
            result *= i;
        }
        return result;
    }
    
    public static void Main(string[] args)
    {
        Console.WriteLine(factorial(5)); // 也是回傳 120
    }
}

既然我們使用迴圈陳述式已經能得到同樣的結果,那為什麼我們還需要使用遞迴函式呢?如果比較兩個方法的話,我們會發現遞迴函式使用的變數量較迴圈陳述式少,那這就是我們使用遞迴函式的理由嗎?可以說是,也可以說不是。雖然遞迴函式使用的變數量比較少,可是實際比較記憶體的使用量後我們會發現程式在遞迴的過程中呼叫的多層函式所花費的記憶體往往更多。不過另一方面,由於變數的數量減少了,程式的複雜度也會相應的減少了一點,這可以讓我們減少遇到在較複雜的迴圈中難以閱讀及維護的狀況。

那我們應該什麼時候使用迴圈、什麼時候使用遞迴呢?這個問題並沒有固定的答案,尤其是在我們面對複雜的問題時更是如此。不過,在這種時候,遞迴的優勢便是我們可以把複雜的問題拆開成多個較小的部分來看,這部分就讓我們明天繼續討論吧。


上一篇
[DAY 17] 命名是一種藝術
下一篇
[DAY 19] 分治法:學會把問題拆開
系列文
程式基礎概念討論30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言