Kotlin 尾遞迴優化

2021-10-19 14:24:18 字數 3073 閱讀 4981

尾遞迴就是函式在呼叫完自己之後沒有其他操作的遞迴,是遞迴的一種特殊形式。舉個例子,"計算斐波那契數列第 n 項"的遞迴演算法有哪些?

斐波那契數列第 0、1 位都是 1,從第二位開始,每項是前兩位之和,因此用遞迴演算法很容易就能實現出來了:

fun

fib1

(n: int)

: int

這種寫法雖然遞迴呼叫是在方法的最後一行,但其實這裡還有結果相加的操作,並不符合尾遞迴的定義。

簡單遞迴雖然容易理解,但實際上,該演算法會有冗餘計算,比如:fib1(2)會被執行多次,如果 n 越大,這種冗餘計算就會越多:

為了解決上述簡單遞迴實現的弊端,我們可以把已經計算過的結果儲存起來,傳遞給下次計算,所以可以將遞迴寫法進行優化:

fun

fib2

(n: int)

: int

funfibiter

(a: int, b: int, n: int)

: int

else

}

其中,fibiter()的遞迴**在方法的最後一行,呼叫完也沒有其他的操作,符合尾遞迴的定義。

理論歸理論,我們還是得用實際**來測試一下兩種遞迴演算法的執行耗時情況,這種才更能直**出差別,為了方便測試,這裡寫了乙個耗時測試方法:

fun

timeconsume

(operation:()

-> unit)

ms , end = $

ms , 耗時 $

ms")

}

分別將兩種遞迴演算法丟到耗時測試方法timeconsume()中,得到測試結果:

fun

main

(args: array

)// 1836311903

// begin = 1612368480299ms , end = 1612368486217ms , 耗時 5918ms

timeconsume

// 1836311903

// begin = 1612368486217ms , end = 1612368486217ms , 耗時 0ms

}

為了拿到斐波那契數列第 45 個元素值,fib1() 耗時近 6s,而 fib2() 耗時 0ms,這是何等的差距。

注意:測試 fib1(50) 會記憶體溢位。

雖然上述尾遞迴演算法的耗時很小,但我們知道,遞迴演算法效率其實並不高,因為每遞迴一次就要開闢乙個方法棧,這是有效能消耗的,還有可能因為遞迴次數過多導致出現記憶體溢位的情況,而迭代演算法就沒有這種問題:

fun

fib3

(n: int)

: int

return a

}

同樣的,我們來對尾遞迴演算法和迭代演算法進行耗時測試:

fun

main

(args: array

)// 690383169

// begin = 1612369032575ms , end = 1612369032578ms , 耗時 3ms

timeconsume

// 690383169

// begin = 1612369032578ms , end = 1612369032579ms , 耗時 1ms

}

理論與實際相結合,通過測試結果可以得知,尾遞迴演算法和迭代演算法的差距還是有的,如果電腦 cpu 效能較低,或者方法中存在記憶體操作,這個差距會更大。

注意:因為"計算斐波那契數列第 n 項"這個演算法題目僅僅只是數值執行,對於這 2 個演算法來說太 easy 了,都是毫秒級別的,所以,需要取較後的元素這樣計算量會多一點才能看出差距,同時因為遞迴過多會出現記憶體溢位,因此 n 的取值也不能太大,測試 15000 會記憶體溢位,12000 則不會。

既然遞迴有這種缺點,那麼我們以後就杜絕使用遞迴演算法吧?當然不行,遞迴也有乙個很大的優點,那就是**邏輯理解容易,既然這樣,那有沒有辦法讓遞迴演算法的效能跟迭代演算法一樣呢?還真有,kotlin 提供了tailrec關鍵字,可以讓尾遞迴演算法在編譯期自動進行**優化,從而解決尾遞迴演算法的缺點。我們將 fibiter() 加上tailrec關鍵字:

fun

fib2

(n: int)

: int

// 只加了 tailrec 關鍵字

tailrec

funfibiter

(a: int, b: int, n: int)

: int

再來測試 fib2() 與 fib3() 兩個演算法的耗時情況:

fun

main

(args: array

)// -1256600222

// begin = 1612370134450ms , end = 1612370134451ms , 耗時 1ms

timeconsume

// -1256600222

// begin = 1612370134452ms , end = 1612370134453ms , 耗時 1ms

}

這原本傳入 15000 就會出現記憶體溢位的尾遞迴演算法 fib2(),現在居然能傳入 50000 了,耗時也與迭代演算法 fib3() 一樣,這就是tailrec關鍵字的厲害之處。

注意:tailrec關鍵字只能優化尾遞迴演算法,其它遞迴演算法無法優化。

Kotlin尾遞迴優化

一 尾遞迴優化 1.遞迴的一種特殊形式 2.呼叫自身後無其他的操作 3.tailrec關鍵字提示編譯器尾遞迴優化 二 具體的來看看一下 說明 package net.println.kotlin.chapter5.tailrecursive author wangdong description 定...

Kotlin尾遞迴優化

尾呼叫 tail call 是函式式程式設計的乙個重要概念,本文介紹它的含義和用法。如果乙個函式中所有遞迴形式的呼叫都出現在函式的末尾,我們稱這個遞迴函式是尾遞迴的。當遞迴呼叫是整個函式體中最後執行的語句且它的返回值不屬於表示式的一部分時,這個遞迴呼叫就是尾遞迴。簡單地說,尾遞迴就是某個函式的最後一...

Kotlin筆記19 尾遞迴優化

前面了我學習了kotlin的遞迴,那麼我還接觸到了kotlin的尾遞迴優化。什麼是尾遞迴優化呢?帶著疑問更好去學習。1.尾遞迴是遞迴的一種特殊形式 2.呼叫自身無其他操作 3.tailrec關鍵字提示編譯器尾遞迴優化 demo中有使用tailrec關鍵字進行提示編譯器尾遞迴優化。尾遞迴演示 實現方式...