C#中尾遞歸的使用、優(yōu)化及編譯器優(yōu)化
遞歸運(yùn)用
一個(gè)函數(shù)直接或間接的調(diào)用自身,這個(gè)函數(shù)即可叫做遞歸函數(shù)。
遞歸主要功能是把問題轉(zhuǎn)換成較小規(guī)模的子問題,以子問題的解去逐漸逼近最終結(jié)果。
遞歸最重要的是邊界條件,這個(gè)邊界是整個(gè)遞歸的終止條件。
static int RecFact(int x)
{
if (x == 0)
return 1;
return x * RecFact(x - 1);
}
RecFact(10);
上面是個(gè)經(jīng)典階乘函數(shù)的實(shí)現(xiàn)。這里分2步:
1.轉(zhuǎn)換,把10的階乘轉(zhuǎn)化成10*9!,10(9*8!)....每次轉(zhuǎn)換規(guī)模就變的更小。
2.逼近,轉(zhuǎn)換到最小規(guī)模時(shí)0!,求解1。開始逆向合并逐漸逼近到10,得出解。
這里的x==0就是我們的邊界條件(即終止條件),也有的依賴外部變量為邊界。
如果一個(gè)遞歸函數(shù)沒有邊界,也就無法停止(無限循環(huán)至內(nèi)存溢出),當(dāng)然這樣也沒什么意義。
RecFact調(diào)用堆棧:
常見使用場景:
1.階乘/斐波那契數(shù)列/漢諾塔
2.遍歷硬盤文件
3.InnerExceptions異常撲捉(exception.InnerException==null)
尾遞歸優(yōu)化
當(dāng)邊界不明確的時(shí)候,遞歸就很容易出現(xiàn)溢出問題。
在階乘過程中,堆棧需要保存每次(RecFact)調(diào)用的返回地址及當(dāng)時(shí)所有的局部變量狀態(tài),期間堆棧空間是無法釋放的(即容易出現(xiàn)溢出)。
為了優(yōu)化堆棧占用問題,從而提出尾遞歸優(yōu)化的辦法。
static void TailRecursion(int x)
{
Console.WriteLine(x);
if (x == 10)
return;
TailRecursion(x + 1);
}
TailRecursion(0);
使用尾遞歸堆??梢圆挥帽4嫔洗蔚暮瘮?shù)返回地址/各種狀態(tài)值,而方法遺留在堆棧上的數(shù)據(jù)完全可以釋放掉,這是尾遞歸優(yōu)化的核心思想。
由于尾遞歸期間,堆棧是可以釋放/再利用的,也就解決遞歸過深而引起的溢出問題,這也是尾遞歸的優(yōu)勢所在。
編譯器優(yōu)化
尾遞歸優(yōu)化,看起來是蠻美好的,但在net中卻有點(diǎn)亂糟糟的感覺。
1.Net在C#語言中是JIT編譯成匯編時(shí)進(jìn)行優(yōu)化的。
2.Net在IL上,有個(gè)特殊指令tail去實(shí)現(xiàn)尾遞歸優(yōu)化的(F#中)。
我們執(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),如下:
let rec TailRecursion(x) =
if (x = 1000) then true
else TailRecursion(x + 1)
(方便演示C#呈現(xiàn))編譯器優(yōu)化成:
public static bool TailRecursion(int x)
{
while (x != 0x3e8)
{
x++;
}
return true;
}
2、 復(fù)雜的尾遞歸,F(xiàn)#編譯器會(huì)生成IL指令Tail進(jìn)行優(yōu)化,如下:
let TailRecursion2 a cont = cont (a + 1)
優(yōu)化成:
如何定義復(fù)雜的尾遞歸呢?通常是后繼傳遞模式(CPS)。
F#中在debug模式下,需要在編譯時(shí)配置:
總結(jié)
在C#語言(過程式/面向?qū)ο缶幊趟枷?中,優(yōu)先考慮的是循環(huán),而不是遞歸/尾遞歸。 但在函數(shù)式編程思想當(dāng)中,遞歸/尾遞歸使用則是主流用法,就像在C#使用循環(huán)一樣。
相關(guān)文章
C#中Foreach循環(huán)遍歷的本質(zhì)與枚舉器詳解
這篇文章主要給大家介紹了關(guān)于C#中Foreach循環(huán)遍歷本質(zhì)與枚舉器的相關(guān)資料,foreach循環(huán)用于列舉出集合中所有的元素,foreach語句中的表達(dá)式由關(guān)鍵字in隔開的兩個(gè)項(xiàng)組成,本文通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考下2021-08-08C#?將數(shù)據(jù)庫SqlServer數(shù)據(jù)綁定到類中的過程詳解
本文講述的是讀取數(shù)據(jù)庫中數(shù)據(jù)的常用做法,即將數(shù)據(jù)庫中的數(shù)據(jù)綁定到創(chuàng)建的類中,再將類綁定到DataGridView的數(shù)據(jù)源中的做法,對(duì)C#將SqlServer數(shù)據(jù)綁定到類中感興趣的朋友一起看看吧2022-06-06WinForm實(shí)現(xiàn)最小化到系統(tǒng)托盤方法實(shí)例詳解
這篇文章主要介紹了WinForm實(shí)現(xiàn)最小化到系統(tǒng)托盤方法,實(shí)例分析了C#中實(shí)現(xiàn)WinForm最小化到系統(tǒng)托盤所需的相關(guān)控件與使用技巧,需要的朋友可以參考下2015-05-05C#自動(dòng)給文章關(guān)鍵字加鏈接實(shí)現(xiàn)代碼
這篇文章主要介紹了C#自動(dòng)給文章關(guān)鍵字加鏈接實(shí)現(xiàn)代碼,有需要的朋友可以參考一下2013-12-12C# Winform中DataGridView導(dǎo)出為Excel的實(shí)現(xiàn)示例
本文主要介紹了C# Winform中DataGridView導(dǎo)出為Excel的實(shí)現(xiàn)示例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-05-05C#開發(fā)Android百度地圖手機(jī)應(yīng)用程序(多地圖展示)
這篇文章主要介紹了C#開發(fā)Android百度地圖手機(jī)應(yīng)用程序(多地圖展示)的相關(guān)資料,需要的朋友可以參考下2016-02-02積累Visual Studio 常用快捷鍵的動(dòng)畫演示
在代碼開發(fā)過程中,頻繁的使用鍵盤、鼠標(biāo)操作非常麻煩,影響程序的開發(fā)效率。如何操作能用鍵盤來操作,那就節(jié)省時(shí)間了。下面小編把我平時(shí)積累的有關(guān)visul studio 常用快捷鍵的動(dòng)畫演示分享給大家,僅供大家參考2015-10-10