如果乙個函式中所有遞迴形式的呼叫都出現在函式的末尾,我們稱這個遞迴函式是尾遞迴的。當遞迴呼叫是整個函式體中最後執行的語句且它的返回值不屬於表示式的一部分時,這個遞迴呼叫就是尾遞迴。尾遞迴函式的特點是在回歸過程中不用做任何操作,這個特性很重要,因為大多數現代的編譯器會利用這種特點自動生成優化的**。
原理:當編譯器檢測到乙個函式呼叫是尾遞迴的時候,它就覆蓋當前的活動記錄而不是在棧中去建立乙個新的。編譯器可以做到這點,因為遞迴呼叫是當前活躍期內最後一條待執行的語句,於是當這個呼叫返回時棧幀中並沒有其他事情可做,因此也就沒有儲存棧幀的必要了。通過覆蓋當前的棧幀而不是在其之上重新新增乙個,這樣所使用的棧空間就大大縮減了,這使得實際的執行效率會變得更高。
例項:以階乘的計算為例,尾遞迴實現演算法如下——
int facttail(int n, int a)
示例中的函式是尾遞迴的,因為對facttail的單次遞迴呼叫是函式返回前最後執行的一條語句。在facttail中碰巧最後一條語句也是對facttail的呼叫,但這並不是必需的。換句話說,在遞迴呼叫之後還可以有其他的語句執行,只是它們只能在遞迴呼叫沒有執行時才可以執行。
尾遞迴是極其重要的,不用尾遞迴,函式的堆疊耗用難以估量,需要儲存很多中間函式的堆疊。比如f(n, sum) = f(n-1) + value(n) + sum; 會儲存n個函式呼叫堆疊,而使用尾遞迴f(n, sum) = f(n-1, sum+value(n)); 這樣則只保留後乙個函式堆疊即可,之前的可優化刪去,而編譯器正會這麼做處理以優化**。編譯器會將這些呼叫進行優化使之變為簡單的跳轉,從而節省函式呼叫在時間和空間上的開銷,提高執行效率。
尾遞迴有時會等同於乙個回到函式開始位置的迴圈,因此,有時也使用尾遞迴來代替常見的迴圈、goto或continue語句,不過並不多見。
為了更好地理解尾遞迴,下面再給個例子:
//線性遞迴, 斐波那契數列的遞迴實現屬於指數遞迴(2^n)
long rescuvie(long n)
/*
尾遞迴long
tailrescuvie
(longn,
longa)
long
tailrescuvie
(longn)
當n = 5時
對於線性遞迴, 他的遞迴過程如下:
rescuvie(5)
}
}}
}}}
}}}
}}
}
120
對於尾遞迴, 他的遞迴過程如下:
tailrescuvie(5)
tailrescuvie(5, 1)
tailrescuvie(4, 5)
tailrescuvie(3, 20)
tailrescuvie(2, 60)
tailrescuvie(1, 120)
120
很容易看出, 普通的線性遞迴比尾遞迴更加消耗資源, 在實現上說, 每次重複的過程
呼叫都使得呼叫鏈條不斷加長. 系統不得不使用棧進行資料儲存和恢復.而尾遞迴就
不存在這樣的問題, 因為他的狀態完全由n和a儲存.
*/
DATA GUARD的概念及作用
data guard的最主要的功能是冗災。當然根據配置的不同,data guard還可以具備以下特點 高可用 效能提公升 資料保護以及故障恢復等。data guard可以分為物理standby和邏輯standby兩種。二者的最大差別在於,物理standby應用的是主庫的歸檔日誌,而邏輯standby...
BFC的概念及作用
在了解什麼是bfc之前,首先得明白什麼是box formatting context 乙個決定如何渲染文件的容器 的概念 box css布局的基本單位 box是 css 布局的物件和基本單位,直觀點來說,就是乙個頁面是由很多個 box組成的,元素的型別和display屬性,決定這個box的型別,不同...
遞迴演算法概念及案例
1.什麼是遞迴演算法 遞迴演算法就是直接或間接呼叫自己的演算法。案例用遞迴函式和棧操作逆序棧 乙個棧依次壓入1,2,3,4,5那麼從棧頂到棧底分別為5,4,3,2,1。將這個棧轉置後,從棧頂到棧底為1,2,3,4,5,也就是實現了棧中元素的逆序,請設計乙個演算法實現逆序棧的操作,但是只能用遞迴函式來...