要理解遞迴就要先理解遞迴:手把手教你寫遞迴 阿新 • • 發佈:2020-12-18 問:何為遞迴函式? 說人話:**自己呼叫自己的函式就叫遞迴函式**。 ## 遞迴函式寫法 實現一個遞迴函式,我將其概括為是一個“推卸責任”的過程,分為3個步驟: 1. **列出方法簽名**:明確該函式/方法的輸入輸出,由此寫出它的方法簽名(method’s signature),方法簽名包括修飾符,返回值型別,方法名和引數列表。 2. 完成**邊界情況**(base case,也稱基準情況):即找到“替罪羊”,因為不停地把問題往下級推卸,最後總得有人出來背鍋。 3. 完成**遞迴情況**(recursive case):即完成責任推卸鏈,把問題從第$n$層推卸到第$n - 1$層。 我們將用求階乘的例子來詳細解釋這3個步驟。 ### 列出方法簽名 首先,明確輸入輸出:我們希望只要給該方法/函式一個整數,就能返回它的階乘。那麼該遞迴方法的方法簽名即為如下: ```java public int factorial(int n){}; ``` 這樣我們就完成了實現遞迴函式的第1個步驟了。 ### 完成邊界情況 很顯然階乘的邊界情況就是求1的階乘,它沒有理由再把計算的責任推卸給任何人了,因為它自己就能直接得出計算結果1,所以求1的階乘就是我們要找的那隻“替罪羊”。 因此,基準情況即為如下: ```java if (n == 1) return n; ``` ### 完成遞迴情況 提前找好了“替罪羊”,接下來我們只需要把“責任推卸鏈”完成,讓責任一級一級最終推卸到替罪羊身上,就可以大功告成! 而要完成這條“責任推卸鏈”,其實就是**列出關於原問題和子問題的數學等價關係式**。 對於求階乘而言,原問題和子問題的等價關係式為:$n! = n \times (n - 1)!$。這樣,就將問題從第$n$層成功地轉移到了第$n-1$層。 那麼對應的程式碼實現即為如下: ```java else return n * factorial(n - 1); ``` 綜合以上3個步驟,完整版的實現階乘的遞迴函式即為: ```java /** Returns n factorial. */ public int factorial(int n) { // Base case if (n == 1) return n; // Recursive case else return n * factorial(n - 1); } ``` 在這個遞迴函式裡,我們定義了遞迴情況來一步步簡化問題,也定義邊界情況來計算出最終的邊界值。所以我們大可放心地呼叫該函式,讓它自行去解決問題。 看到這裡,相信你已經掌握了遞迴函式的寫法。可以看出,寫出一個遞迴函式並沒有那麼容易。而我們可不希望當我們絞盡腦汁憋出一個遞迴函式後,卻換來老闆一句“多此一舉,淨瞎折騰”。 那要避免這種情況,我們得先弄清楚一個前提:什麼時候派上遞迴最為合適? ## 什麼時候應用遞迴 我們先來談談遞迴的缺點: 1. **對空間消耗大,容易導致棧溢位** 遞迴是呼叫自身的函式,而函式的每次呼叫都會在棧中產生呼叫幀(call frame, 用來儲存內部變數、返回點等資訊),可棧的空間是有限的,沒法一次同時儲存過多的呼叫幀。所以如果呼叫的次數多了就容易導致棧溢位,對空間消耗大。 2. **對時間消耗大,導致執行效率低** 首先,往棧中壓入和彈出資料需要時間;此外,遞迴中經常會產生很多重複計算,例如在斐波那契數列的遞迴實現中,當`n = 5`時,需要計算一遍的`fibonacci(3)`,推導到`n = 4`時,又需要計算一遍`fibonacci(3)`。也就是說求一個5的階乘需要計算兩遍的`fibonacci(3)`,所以說時間效率低。 (可以利用一個數組或雜湊map來儲存已計算出的結果來避免重複計算) 而遞迴主要是用於以下兩種情況: 1. **資料結構本身就是按遞迴的形式定義的。** 例如斐波那契數列、n的階乘、二叉樹的遍歷、圖的搜尋等。這種情況下,以其人之道還治其人之身當然是很直觀了當的做法。 2. **問題能以同樣的解法逐級減小問題規模** 例如分治演算法、回溯演算法等,它們能用同樣的解法去一步步減小問題規模,所以用遞迴實現起來就很方便。 所以,除非是以上兩種情況,否則儘量避免使用遞迴,以避免對空間和時間產生大的消耗。(P.S. 把浪費的這些功夫拿去打王者榮耀它不香嗎...) ## 總結 遞迴函式是自己呼叫自己的函式。通過列出方法簽名,完成基準情況和遞迴情況即可完成一個遞迴函式。但是用遞迴實現通常會對時間和空間產生大的消耗,因此除非資料結構本身就是按遞迴的形式定義或是問題能以同樣的解法逐級減小問題規模,否則應儘量避免使用遞迴。 ## 參考 1. https://mp.weixin.qq.com/s/tqGKHZzSyDBgEp-oWsOztQ ***創作不易,點個贊再走叭~***