標籤:blog http strong os 資料 2014
線性遞迴,就是大家平常說的遞迴,線性遞迴函式的最後一步操作不是遞迴操作,將最終條件代入計算。在每次遞迴調用時,遞迴函式中的參數,局部變數等都要儲存在棧中,當資料量很大的時候,會造成棧溢出。
尾遞迴,也就是線性迭代,尾遞迴函式的最後一步操作是遞迴,也即在進行遞迴之前,把全部的操作先執行完,這樣的好處是,不用花費大量的棧空間來儲存上次遞迴中的參數、局部變數等,這是因為上次遞迴操作結束後,已經將之前的資料計算出來,傳遞給當前的遞迴函式,這樣上次遞迴中的局部變數和參數等就會被刪除,釋放空間,從而不會造成棧溢出。但是很多編譯器並沒有自動對尾遞迴最佳化的功能,也即當編譯器判斷出當前所執行的操作是遞迴操作時,不會理會它究竟是線性遞迴還是尾遞迴,這樣也就不會刪除掉之前的局部變數和參數等。另外,尾部遞迴一般都可轉化為迴圈語句。
我們用階乘的例子來看這兩者的區別:
階乘: 5!=1*2*3*4*5 結果為:120
線性遞迴:
long Rescuvie(long n)
{
return(n == 1) ? 1 : n * Rescuvie(n - 1);
}
調用過程如:
當n = 5時
對於線性遞迴, 他的遞迴過程如下:
Rescuvie(5)
開始調用
{5 * Rescuvie(4)}
{5 * {4 * Rescuvie(3)}}
{5 * {4 * {3 * Rescuvie(2)}}}
{5 * {4 * {3 * {2 * Rescuvie(1)}}}}
{5 * {4 * {3 * {2 * 1}}}}
{5 * {4 * {3 * 2}}}
{5 * {4 * 6}}
{5 * 24}
120
尾遞迴:
long TailRescuvie(long n, long a) {
return(n == 1) ? a : TailRescuvie(n - 1, a * n);
}
對於尾遞迴, 他的遞迴過程如下:
TailRescuvie(5)
TailRescuvie(5, 1)
TailRescuvie(4, 5)
TailRescuvie(3, 20)
TailRescuvie(2, 60)
TailRescuvie(1, 120)
120
尾遞迴易於在編譯器層面最佳化。如果編譯器未作最佳化,效果和線性遞迴差不多。
分別用線性遞迴和尾遞迴來解決經典的猴子吃桃問題
猴子第一天摘下若干個桃子,當即吃了一半,還不過癮就多吃了一個。第二天早上又將剩下的桃子吃了一半,還是不過癮又多
吃了一個。以後每天都吃前一天剩下的一半再加一個。到第10天剛好剩一個。問猴子第一天摘了多少個桃子?
線性遞迴方式
#include <iostream>using namespace std;int AllSum(int day){if(day==10)return 1;elsereturn 2*AllSum(day+1)+2;}int main(){int sum=AllSum(1);cout<<"第一天共摘了"<<sum<<"個桃子"<<endl;return 0;}
運行結果:
尾遞迴的實現方式
#include<iostream>using namespace std;int AllSum(int day,int total){if(day==10)return total;elsereturn AllSum(day+1,total*2+2);}int main(){int sum=AllSum(1,1);cout<<"第一天共摘了"<<sum<<"個桃子"<<endl;return 0;}
運行結果:
關於線性遞迴和尾遞迴深度的研究學習可以參考 http://blog.zhaojie.me/2009/04/tail-recursion-explanation.html