1. 程式人生 > >為什麼用 遞迴 計算“階乘”和“斐波那契數列”是不合適的?

為什麼用 遞迴 計算“階乘”和“斐波那契數列”是不合適的?

        我們看到的參考書中,當講到遞迴時基本上都是使用“階乘”和“斐波那契數列”來舉例子,確實可以幫助我們瞭解遞迴,但卻導致我們在平時編寫類似程式時,總是使用遞迴來實現。那麼在實際專案中,使用遞迴來實現這兩個程式到底是否合適?答案是否定的。

        《C和指標》的作者Kenneth A. Reek說,他認為這是很不幸的:“計算階乘時不能提供任何優越之處;在斐波那契數列中,使用遞迴效率非常非常低”。  尤其對於計算斐波那契數,使用遞迴和使用迭代的效率有可能相差幾十萬倍,下面有程式碼分析。

1. 技巧

    1.1 “尾部遞迴”可以使用“迭代”來替換。 學過資料結構之後,對這個應該不陌生。

    1.2   遞迴的優點在於易於理解迭代的優點在於效率高。如何選擇取決於你在“易於理解”和“效率”中如何作出權衡。

            遞迴函式呼叫涉及一些執行時開銷,包括引數必須壓到堆疊中、為區域性變數分配記憶體、暫存器的值必須儲存等等,並且在每次呼叫返回時,上述儲存操作必須還原,恢復成呼叫前的樣子。迭代的實現方式開銷顯然要小。所以在可理解性相差不大的情況下,為了保證效率應該優先選擇迭代

2. 階乘

         階乘的一種定義方式為:

F(n) = 1        當 n<=0
F(n) = n * F(n-1)       當n>0

    2.1  遞迴實現

          實現程式碼如下

/* 利用 遞迴 來計算整數n的階乘 */
long factorial_recursion( int n ){
    if( n<=0 ){
        return 1;
    }else{
        return n*factorial_recutsion( n-1 ); 
    } 
}

         很明顯是“尾部遞迴”,所以我們可以很輕鬆的改寫為“迭代”的形式。

    2.2   迭代實現

         迭代實現程式碼如下

/* 利用 迭代 來計算整數n的階乘 */ 
long factorial_iteration( int n ){
    int result = 1;
    while( n>1 ){
        result *=n;
        n--;
    }
    return result;
}

    2.3   兩種方式的比較

        很明顯,兩種實現方式都非常簡單易懂,在實際專案中,為了效率,應該優先選擇 迭代

3. 斐波那契數列

        斐波那契數列中的每個數,都是它前面兩個數的和。其計算公式如下:

F(0) = 0
F(1) = 1
F(n)= F(n-1) + F(n-2)

    3.1  遞迴實現

          遞迴實現程式碼如下

/* 計算斐波那契數列的第num個數 */
int fibonacci_count( int num ){
    if( num<=2 ){
        return 1;
    }else{
        return fibonacci_count(num-1) + fibonacci_count(num-2);
    }
}

    3.2  迭代實現

           迭代實現程式碼如下

/* 
利用 迭代 計算 斐波那契數 
其形式為: 1, 1, 2, 3, 5, 8, 13, 21, 34 … 
*/
long fibonacci(int n){
    long result;
    long previous;
    long next;
    
    result = previous = 1;
    
    if(n<=2){
        return 1;
    }
    int i =3; /* 用於計算已經計算到第幾個 */ 
    while( i<=n ){
        next = result;
        result = previous + next;
        previous = next;
        i++;
    } 
    return result;    
}

    3.3  兩種方式比較

        下面一個程式反映了,利用遞迴方式計算斐波那契函式時,呼叫fibonacci函式的次數,以及計算fibonacci(3)的次數。

#include <stdio.h>
#include <stdlib.h> 

int fibonacci_count( int num );

long count;    /* 用來記錄斐波那契數列中計算呼叫 fibonacci_rec_count函式 的次數  */
int num_three; /* 計算 fibonacci_rec_count(3)的次數 */
int main(){
    printf("Fibonacci(num)/t/tNumber of Calls/t/tnumbers of Call fibonacci_rec_count(3)   /n");
    int i;
    for( i=1; i<=30; i++ ){
        int result = fibonacci_rec_count(i);
        printf("num: %d/t/t/tcount: %ld   /t/t%d/n", i, count, num_three);
        count = 0;
        num_three = 0;
    } 
    
    getchar();
    return EXIT_SUCCESS;
}
/* 計算斐波那契數列的第num個數 */
int fibonacci_rec_count( int num ){
    count++;    /* 每呼叫一次fibonacci_rec_count函式,count的值便加1  */
    if( num==3 ){
        num_three++;
    } 
    if( num<=2 ){
        return 1;
    }else{
        return fibonacci_rec_count(num-1) + fibonacci_rec_count(num-2);
    }
}

執行結果為:

Fibonacci(num)          Number of Calls         numbers of fibonacci(3)
num: 1                  count: 1                0
num: 2                  count: 1                0
num: 3                  count: 3                1
num: 4                  count: 5                1
num: 5                  count: 9                2
num: 6                  count: 15               3
num: 7                  count: 25               5
num: 8                  count: 41               8
num: 9                  count: 67               13
num: 10                 count: 109              21
num: 11                 count: 177              34
num: 12                 count: 287              55
num: 13                 count: 465              89
num: 14                 count: 753              144
num: 15                 count: 1219             233
num: 16                 count: 1973             377
num: 17                 count: 3193             610
num: 18                 count: 5167             987
num: 19                 count: 8361             1597
num: 20                 count: 13529            2584
num: 21                 count: 21891            4181
num: 22                 count: 35421            6765
num: 23                 count: 57313            10946
num: 24                 count: 92735            17711
num: 25                 count: 150049                   28657
num: 26                 count: 242785                   46368
num: 27                 count: 392835                   75025
num: 28                 count: 635621                   121393
num: 29                 count: 1028457                  196418
num: 30                 count: 1664079                  317811

        在遞迴實現方式中,每次遞迴呼叫都會觸發另外兩個遞迴呼叫,而且這兩個呼叫的任何一個又會觸發兩個遞迴呼叫,依次越來越多。冗餘的計算增長迅速。例如:在計算fibonacci(10) 時,fibonacci(3)被計算了21次;但在計算fibonacci(30) 時,fibonacci(3)被計算了 317811 次;這 317811 次計算結果完全一樣,除了一次有用外,其餘都是浪費。

參考資料:《C和指標》