1. 程式人生 > >C++ 尾遞迴

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,差不多。