C++中的遞歸

1.概念

        遞歸函數即自調用函數,在函數內部直接的或者間接地調用自己。在求解某些具有隨意性的複雜問題時經常使用遞歸,如要求編寫一個函數,將輸入的任意長度的字符串反向輸出。普通做法是將字符串放入數組中然後將數組元素反向輸出即可,然而這裏的要求是輸入是任意長度的,總不能開闢一個很大的空間保存字符串吧?這時候遞歸就起作用了。遞歸採用了分治的思想,將整體分割成部分,從最小的基本部分入手,逐一解決,其中部分通常和整體具有相同的結構,這樣部分可以繼續分割,直到最後分割成基本部分。

        遞歸函數必須定義一個終止條件,即什麼情況下終止遞歸,終止繼續調用自己,如果沒有終止條件,那麼函數將一直調用自己,知道程序棧耗盡,這時候等於是寫了一個Bug!

總結遞歸的特點:

(1) 使用遞歸時,一定要有明確的終止條件!

(2) 遞歸算法解題通常代碼比較簡潔,但不是很容易讀懂。

(3) 遞歸的調用需要建立大量的函數的副本,尤其是函數的參數,每一層遞歸調用時參數都是單獨的佔據內存空間,他們的地址是不同的,因此遞歸會消耗大量的時間和內存。而非遞歸函數雖然效率高,但相對比較難編程。

(4) 遞歸函數分爲調用和回退階段,遞歸的回退順序是它調用順序的逆序。

2.實踐

斐波那契數列當n>3時,第n個元素的值等於第n-1個元素和n-2個元素的和,當n不確定具體數值時,可以通過遞歸的方式實現

int Fib(int n) {
    if (n < 2)
        return 1;

    return Fib(n - 1) + Fib(n - 2);
}
void test_fib(int n) {
    int fib1[n], fib2[n];
    fib1[0] = 1;
    fib1[1] = 1;
    fib2[0] = 1;
    fib2[1] = 1;
    for (int i = 2; i < n; i++) {
        fib1[i] = Fib(i);
        fib2[i] = fib2[i - 1] + fib2[i - 2];
    }
    cout << "use func Fib() " << endl;
    for (int i = 0; i < n; i++) {
        cout << fib1[i] << ' ';
    }
    cout << endl;
    cout << "use for loop " << endl;
    for (int i = 0; i < n; i++) {
        cout << fib2[i] << ' ';
    }
    cout << endl;
}

最終由遞歸得到的斐波那契數列和由for循環得到的數列相同。

階乘問題同樣可以通過遞歸實現,代碼爲

int Factorial(int n) {
    if (n == 1)
        return 1;
    return n * Factorial(n - 1);
}

當n=5時,函數的調用過程如下圖所示


漢諾塔問題是指一共有3根針,其中兩根爲空,另外一根針從上到下按照尺寸穿好了若干個盤子,上面的小下面的大,要求是每次移動一個盤子,將所有的盤子移動到另一根針上,並且所有的針上的盤子都滿足上小下大的要求,如下圖


這個問題同樣可以使用遞歸的方式解決,思路如下



因此發現以上步驟實際上是一個重複的過程,則整個問題可以使用遞歸解決,當盤子個數爲1時直接移動即可,爲n時則先借助一根針將n-1個盤子移動到另一根針上,而n-1根針可以先移動n-1-1根針,如此往復。代碼如下

void move(int n, char x, char y, char z) {
    // 將n個盤子從x藉助y移動到z上
    if (1 == n) {
        cout << x << "-->" << z << endl;
    } else {
        // 將n-1個盤子從x藉助z移動到y上
        move(n - 1, x, z, y);
        // 將第n個盤子從x移動到z上
        cout << x << "-->" << z << endl;
        // 將n-1個盤子從y藉助x移動到z上
        move(n - 1, y, x, z);
    }
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章