遞歸運(yùn)用 一個函數(shù)直接或間接的調(diào)用自身,這個函數(shù)即可叫做遞歸函數(shù)。
上面是個經(jīng)典階乘函數(shù)的實(shí)現(xiàn)。這里分2步:
這里的x==0就是我們的邊界條件(即終止條件),也有的依賴外部變量為邊界。 如果一個遞歸函數(shù)沒有邊界,也就無法停止(無限循環(huán)至內(nèi)存溢出),當(dāng)然這樣也沒什么意義。 RecFact調(diào)用堆棧: 常見使用場景:
尾遞歸優(yōu)化 當(dāng)邊界不明確的時候,遞歸就很容易出現(xiàn)溢出問題。 在階乘過程中,堆棧需要保存每次(RecFact)調(diào)用的返回地址及當(dāng)時所有的局部變量狀態(tài),期間堆??臻g是無法釋放的(即容易出現(xiàn)溢出)。 為了優(yōu)化堆棧占用問題,從而提出尾遞歸優(yōu)化的辦法。
使用尾遞歸堆棧可以不用保存上次的函數(shù)返回地址/各種狀態(tài)值,而方法遺留在堆棧上的數(shù)據(jù)完全可以釋放掉,這是尾遞歸優(yōu)化的核心思想。 由于尾遞歸期間,堆棧是可以釋放/再利用的,也就解決遞歸過深而引起的溢出問題,這也是尾遞歸的優(yōu)勢所在。 編譯器優(yōu)化 尾遞歸優(yōu)化,看起來是蠻美好的,但在net中卻有點(diǎn)亂糟糟的感覺。
我們執(zhí)行 TailRecursion(0)(x==1000000) 得出如下結(jié)論: C#/64位/Release是有JIT編譯器進(jìn)行尾遞歸優(yōu)化的(非C#編譯器優(yōu)化)。 C#/32位或C#/Debug模式中JIT是不進(jìn)行優(yōu)化的。 F#在優(yōu)化尾遞歸也分2種情況: 1、 簡單的尾遞歸優(yōu)化成while循環(huán),如下:
(方便演示C#呈現(xiàn))編譯器優(yōu)化成:
2、 復(fù)雜的尾遞歸,F(xiàn)#編譯器會生成IL指令Tail進(jìn)行優(yōu)化,如下:
優(yōu)化成: 如何定義復(fù)雜的尾遞歸呢?通常是后繼傳遞模式(CPS)。 F#中在debug模式下,需要在編譯時配置: 總結(jié) 在C#語言(過程式/面向?qū)ο缶幊趟枷?中,優(yōu)先考慮的是循環(huán),而不是遞歸/尾遞歸。 但在函數(shù)式編程思想當(dāng)中,遞歸/尾遞歸使用則是主流用法,就像在C#使用循環(huán)一樣。 參考資料 http:///blog/62412678347 http:///questions/15864670/generate-tail-call-opcode DotNet 微信號:iDotNet 打造東半球最好的 .Net 微信號 -------------------------------------- |
|