在學習Functional programming過程中,學到遞迴可以分為兩類:
- Tail recursion
1 | If a function calls itself as its last action, the function’s stack frame can be reused. This is called tail recursion. |
- Tail call
1 | If the last action of a function consists of calling a function (which may be the same), one stack frame would be sufficient for both functions. Such calls are called tail-calls. |
分別以計算最大公因數(gcd)和階層(factorial)為例:
gcd
1
2
3
4
5
6def gcd(a: Int, b: Int): Int = {
if (b == 0)
a
else
gcd(b, a % b)
}factorial
1
2
3
4
5
6def factorial(n: Int): Int = {
if (n == 0)
1
else
n * factorial(n - 1)
}
例如:
1 | gcd(21, 14) -> gcd(14, 7) -> gcd(7, 0) -> 7 |
1 | factorial(5) -> |
可以發現:
- 在gcd範例中,每一步不會依賴上一步的結果,上一步的結果是以參數方式傳入到函式裡面。
- 在factorial範例中,每一步會依賴上一步的結果,所以需要
stack
來記錄每一步的狀態。
等走到盡頭後,再取出stack
內的元素並且計算之,逐一合併結果。
假如執行 factorial(100000)
可以預期會發生 stack overflow
,我們可以將原本的程式改成Tail recursion版本,就不會有 stack overflow
發生。
在Scala中會對Tail recursion做最佳化,或是可以透過 @tailrec
標註此函數是Tail recursion。
- 測試範例:
1 | import java.util.concurrent.TimeUnit |
- 測試結果:
1
21.611 ms
677.4 μs
可以看出來改成Tail recursion版本效率會大幅改善。