Recursion 遞迴
接下來想要緩慢的把自己的 CS 相關基礎補上,多多接觸寬廣的技術知識,就先從遞迴開始吧!
🐳 遞迴的種類
如果一個 function 裡面有 self-calling 的敘述,便稱為遞迴,遞迴概略可以分為三個種類,分別是:
- Direct Recursion
- Indirect Recursion
- Tail Recursion
下面舉一些簡單的例子來說明這三個遞迴。
🦀 Direct Recursion
Direct Recursion,直接遞迴,應該蠻好理解的。如果某個 function 在 function 內部呼叫自己,就可以稱為直接遞迴。可以參考下面的 psuedo code:
1 | void directRecursionFunction() |
🦀 Indirect Recursion
Indirect Recursion,間接遞迴,意思是指多個 module 之間,彼此互相呼叫,形成 calling cycle。例如:假設目前有三個 function:module A
、module B
、module C
,這三個 function 彼此互相呼叫,便會形成間接遞迴,如下圖:
flowchart LR A[module A] --> B[module B] B --> C[module C] C --> A
像上面那種 function 互相 call 來 call 去,互相高度依賴的狀況(高耦合),盡量不要在實際開發中寫出來,會很可怕。
🦀 Tail Recursion
Tail Recursion,尾端遞迴,其實是直接遞迴的一種,只是在 recursion 之後,下一個可執行的敘述就是 END 敘述。會特別把這個種類分出來是因為這種遞迴可以在 compiler 裡面做到最佳化。(最佳化的意思,某種程度上可以理解成「將遞迴改成非遞迴」)
🐳 Recursion v.s. Iteration(Non-recusrion)
- 任何問題的解法必定可以用兩種演算法去解決:遞迴與非遞迴。
- 遞迴與非遞迴演算法兩者可以互相轉換
- 遞迴改為非遞迴,有標準 SOP
- 非遞迴改回遞迴,沒有標準 SOP(需要靈感)
示意圖
graph TB A -. 要修改的話有SOP .-> C C -. 必存在,但沒有SOP .-> A A[Recurstive Algo] --> B[Problem] C[Non-recursive Algo] --> B[Problem]
比較表
遞迴 | 非遞迴 | |
---|---|---|
程式碼 | 較精簡 | 較冗長 |
區域變數、暫存變數 | 使用很少或是沒有 | 使用量多 |
表達問題的能力 | powerful | weak |
除錯 | 困難 | 容易 |
程式執行時間 | 較久,比較沒有效率 | 較短,較有效率 |
memory stack 空間 | 需要額外的 stack 空間支持,所以執行時需要較多的動態空間 | 無需 stack support |
🐳 題目練習
🦀 Factorial N! 階乘
Question 1: Write an Interative function Fac(N) or pseudo code for N!
1 | function fac(n) { |
Question 2: Write a Recursive function Fac(N) or pseudo code for N!
先把階乘的遞迴數學定義寫出來:
然後再寫出遞迴的程式碼:
1 | function fac(n) { |
解遞迴相關問題的訣竅:先想出遞迴的數學定義,再把數學定義轉換成程式碼!
🦀 Fibonacci Number
Definition
Question 1: Write a Recurisive function for Fib(N)
1 | function fib(n) { |
Quesiton 2: Write a Interative function for Fib(N)
1 | function fib(n) { |
🦀 Greatest Common Divisor (GCD) 最大公因數
Definition
用輾轉相除法來計算兩個數字(A, B)的最大公因數,定義如下:
Write the recursive code for GCD(A, B)
1 | function gcd(a, b) { |
🦀 Tower of Hanoi 河內塔
題目敘述
有三個柱子,假設分別叫做 A、B、C,其中 A 柱子上有 n 個大小不同的盤子,這些盤子從上到下按照大小排放,最上面的盤子最小,最下面的盤子最大,現在要把這些盤子從 A 柱子移到 C 柱子,但必須遵守以下規則:
- 每次只能移動一個盤子
- 大的盤子不能放在小的盤子上面
請把所有的移動步驟都 print 出來。
解題思路
1 | A B C |
先從例子開始想,假設目前有 A、B、C 三個柱子,然後有 3 個盤子在 A 柱子上面,則步驟如下:
- move disk 1 from A to C
- move disk 2 from A to B
- move disk 1 from C to B
- move disk 3 from A to C
- move disk 1 from B to A
- move disk 2 from B to C
- move disk 1 from A to C
把步驟分成三個區塊來看:
- 第 1. ~ 3. 步驟是把 1 ~ 2 號的盤子都先從 A 柱子移到 B 柱子
- 第 4. 步驟是把最後一個第 3 號盤子直接從 A 柱子移到 C 柱子
- 接下來是把 B 柱子上的盤子都移到 C 柱子
例外情況:如果只有一個盤子的話,就直接從 A 柱子搬到 C 柱子就可以了。
1 | function hanoi(n, from, to, via) { |
河內塔的遞迴定義
把上面提到的步驟用數學式來表示,
解