C++ 尾遞迴
參考來源:https://blog.csdn.net/fall221/article/details/9158703
用 C++ 重現了一遍。
例子:裴波拉契數列: 1,1,2,3,5,8,。。。
用遞迴求第 n 項:
double fabonacci(int n){//normal recursion
if(n<=0){
cout<<"wrong parameter for fabonacci(n)!"<<endl;
}
else if(n==1){
return 1;
}
else if(n==2){
return 1;
}
else{
return fabonacci(n-1) + fabonacci(n-2);
}
}
上面這個演算法的時間複雜度是 O(2^n),比如
fab(6)
= fab(5) + fab(4)
= fab(4) + fab(3) + fab(3) + fab(2)
= fab(3) + fab(2) + fab(2) + fab(1) + fab(2) + fab(1) + fab(2)
= fab(2) + fab(1) + fab(2) + fab(2) + fab(1) + fab(2) + fab(1) + fab(2)
如果不是從 fab(6) 開始,是從 fab(n) 開始,n 又足夠大,上式中每行 fab() 個數為 1,2,4,8,16,。。。
所以 fab() 被呼叫的次數為 O(2^n) 數量級,時間複雜度為 O(2^n)
double tail_fabonacci(int a, int b, int n){
// tail recursion
if(n<0){
cout<<"wrong parameter for tail_fabonacci(int, double *)!"<<endl;
}
else if(n==0) return a;
else if(n==1) return b;
else{
return tail_fabonacci(b, a+b, n-1);
}
}
這個演算法已經包括了動態規劃的意思,從 fab(0), fab(1) 出發,迭代大約 n 次,即可得到結果,時間複雜度為 O(n),所以演算法本身就更優越。
int main(){
int n;
cout<<"n=";
cin>>n;
time_t t_start=time(0);
cout<<"fabonacci(n)="<<fabonacci(n)<<endl;
time_t t_middle=time(0);
cout<<"tail recursion: fabonacci(n)="<<tail_fabonacci(0,1,n)<<endl;
time_t t_end=time(0);
cout<<"time to calculate normal recursion:"<<t_middle - t_start<<"s"<<endl;
cout<<"time to calculate tail recursion:"<<t_end - t_middle<<"s"<<endl;
cout<<"total time:"<<t_end-t_start<<"s"<<endl;
return 0;
}
編譯: g++ main.cpp -o main.o
執行結果:
n=50
...
time to calculate normal recursion:186s
time to calculate tail recursion:0s
這很正常。下面是重點——關於尾遞迴優化。
如果不加 -O2,編譯時不會自動做尾遞迴優化,(我估計)每次遞迴沒有擦去上次的棧幀,所以空間複雜度還是 O(n)。可做實驗:注掉呼叫 fabonacci(n) 那一行,即只測試尾遞迴,進行編譯
g++ main.cpp -o main.o
./main.o
n=1000000
segmentation fault (core dumped)
據我目測,估計是棧記憶體溢位了。
但如果加上 -O2 進行編譯,就可實現尾遞迴優化
g++ main.cpp -o main.o -O2
./main.o
n=10000000000
tail recursion: fabonacci(n)= -1.07027e+09
time to calculate tail recursion: 3s
上面的結果中,fabonacci(n)= -1.07027e+09 為負值是因為超出了整型範圍。但 n=10^{10} 也可以算出來,說明棧記憶體沒有爆。據我目測,耗時 3s 是正常的,因為 10^10 次加法,cpu 3點幾的GHz,差不多。