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);
}
}