什麼是遞歸:程序調用自身的編程技巧稱爲遞歸(recursion)。遞歸作爲一種算法在程序設計語言中普遍應用。一個過程或函數在其定義或說明中有直接或間接調用自身的一種方法,它一般把一個大型複雜的問題層層轉化爲一個與原問題類似的規模較小的問題來求解,遞歸策略只需少許的程序就可描述出解題過程所須要的屢次重複計算,大大地減小了程序的代碼量。git
遞歸的主要思考方式在於 : 把大事化小
注意:函數在調用的時候會向內存申請空間。(遞歸的過程就是函數的不停調用過程)算法
(2)遞歸的兩個必要條件編程
- 存在限制條件,當知足這個限制條件的時候,遞歸便再也不繼續。
- 每次遞歸調用以後愈來愈接近這個限制條件。
(3)最簡單的遞歸函數:主函數調用主函數數組
#include<stdio.h> int main() { printf("hello world\n"); main(); return 0; }
↑↑↑上述代碼結果:死循環,棧溢出(stack overflow)ide
爲何會出現棧溢出呢?
要想搞懂棧溢出的緣由,首先咱們要明白程序運行過程當中內存的劃分分區函數
每一次函數的調用,都須要在棧區分配必定的空間(也就是說函數調用也在棧區開闢空間),調用次數太多,棧空間不夠分配(被耗幹),致使棧溢出。測試
接受一個整型值(無符號),按照順序打印它的每一位。spa
例如︰輸入∶1234,輸出1234.
要順序打印它的每一位,就須要獲得它的每一位,1234最容易獲得的就是個位
1234 % 10 = 4
1234 / 10 = 123 % 10 = 3
123 / 10 = 12 % 10 = 2
12 / 10 = 1 % 10 = 1
1 / 10 = 0設計
#include<stdio.h> void print(int n) { if (n > 9) { print(n / 10); } printf("%d ", n % 10); } int main() { unsigned int num = 0; scanf("%d", &num);//1234 print(num);//打印1 2 3 4 return 0; //print(1234) //print(123) 4 //print(12) 3 4 //print(1) 2 3 4 // 1 2 3 4 //當()裏面的數字大於9的時候就拆分,小於9,爲個位數的時候中止拆分,進行打印 }
比較上面兩個遞歸函數,咱們能夠看到: 遞歸的兩個必要條件
①存在限制條件,當知足這個限制條件的時候,遞歸便再也不繼續。
②每次遞歸調用以後愈來愈接近這個限制條件。3d
注意:這兩個條件是必要條件,不是充分條件,也就是說遞歸函數必定知足這兩個條件,可是知足這兩個條件不必定是遞歸。
看下面這個例子:
按F10進行調試:
每一次函數的調用,都須要在棧區分配必定的空間,調用次數太多,棧空間不夠分配(被耗幹),致使棧溢出。
因此咱們在寫遞歸代碼的時候,必定要注意如下幾點:
一、不能死遞歸,要有跳出條件,每次遞歸逼近跳出條件
二、遞歸層層不能太深
編寫函數不容許建立臨時變量,求字符串的長度。
若是調用臨時變量:若是用strlen算:
使用遞歸以下⬇⬇⬇⬇⬇⬇
初步解題思路:用了臨時變量count
#include<stdio.h> #include<string.h> //求字符串長度 int my_strlen(char* pr) { int count = 0; while (*pr != '\0') { count++; pr++; } return count; } int main() { char arr1[] = "bit"; //int len = strlen(arr1);//求字符串長度 int len = my_strlen(arr1); //arr1是數組,數組傳參,傳過去的不是整個數組,而是首元素的地址 printf("%d\n", len); return 0; }
這種方式雖然計算出字符串的長度,可是建立了一個臨時變量count,如今使用遞歸的方式來實現:
int my_strlen(char* pr) { if (*pr != '\0') return 1 + my_strlen(pr + 1); else return 0; } //把大事化小 //my_strlen("bit") //1+my_strlen("it") //1+1+my_strlen("t") //1+1+1+my_strlen("\0") //1+1+1+0 = 3
畫圖詳解:
求n的階乘。(不考慮溢出)
#include<stdio.h> #include<string.h> int Fun1(int x)//迭代(循環)實現 { int i = 0; int y = 1; for (i = 1; i <= x; i++) { y = y * i; } return y; } int Fun2(int x)//遞歸實現 { if (x > 1) return x * Fun2(x - 1); else return 1; } int main() { int n = 0; int ret = 0; printf("請輸入一個數:>>\n"); scanf("%d", &n); //ret = Fun1(n);//循環的方式 ret = Fun2(n);//遞歸的方式 printf("%d的階乘爲:%d\n", n, ret); return 0; }
求第n個斐波那契數。(不考慮溢出)
介紹斐波那契數列,斐波那契數列的排列是:1 , 1,2,3,5,8,13,21,34,55,89,......
依次類推下去,你會發現,它後一個數等於前面兩個數的和。
#include<stdio.h> int count = 0; int Fib1(int x)//遞歸實現 { if (x == 3) count++; if (x <= 2) return 1; else return Fib1(x - 1) + Fib1(x - 2); } int Fib2(int x)//函數實現 { int a = 1; int b = 1; int c = 1; while (x > 2) { c = a + b; a = b; b = c; x--; count++; } return c; } int main() { int n = 0; int ret = 0; scanf("%d", &n); //ret = Fib1(n);//求第n個斐波那契數 ret = Fib2(n);//循環實現 printf("%d\n", ret); printf("count = %d\n", count); return 0; }
咱們在主函數裏面寫後續要被調用的某個函數的時候,先假設要用這個函數實現什麼功能,直接去用,以後再去真正定義並實現這個函數。
這種思想叫作:TDD - 測試驅動開發 先去想函數怎麼用,而後再實現。
遞歸結果:
遞歸方式:效率低
循環結果:
能夠看出此時使用遞歸方式並不高效,其計算同一個數好比 3 的次數爲 2 ^ n (遞歸方式)
而使用循環方式(n > 3時) 次數爲 n
那咱們如何改進呢 ?
在調試factorial函數的時候,若是你的參數比較大,那就會報錯 : stack overflow(棧溢出)這樣的信息。系統分配給程序的棧空間是有限的,可是若是出現了死循環,或者(死遞歸),這樣有可能致使一直開闢棧空間,最終產生棧空間耗盡的狀況,這樣的現象咱們稱爲棧溢出。
那如何解決上述的問題 ?
1.將遞歸改寫成非遞歸。
⒉使用static對象替代nonstatic局部對象。在遞歸函數設計中,可使用static對象替代nonstatic局部對象(即棧對象),這不只能夠減小每次遞歸調用和返回時產生和釋放nonstatic對象的開銷,並且static對象還能夠保存遞歸調用的中間狀態,而且可爲各個調用層所訪問。
一、字符串逆序:
編寫一個函數 reverse_string(char* string)(遞歸實現)
實現:將參數字符串中的字符反向排列,不是逆序打印。
要求:不能使用C函數庫中的字符串操做函數。
好比 : char arr[] = 「abcdef」,逆序以後數組的內容變成:fedcba
#include<stdio.h> //#include<string.h> //int my_strlen(char* str)//非遞歸方法求字符串長度 //{ // int count = 0; // while (*str != '\0') // { // count++; // str++; // } // return count; //} int my_strlen(char* str)//遞歸方法求字符串長度 { if (*str != '\0') return 1 + my_strlen(str + 1); else return 0; } //void reverse_string(char* str)//非遞歸方法逆序字符串 //{ // //int len = strlen(str); // int len = my_strlen(str); // int left = 0; // int right = len - 1; // while (left < right) // { // char tmp = *(str+left); // *(str + left) = *(str + right); // *(str + right) = tmp; // left++; // right--; // } // //} void reverse_string(char* str) { int len = my_strlen(str); if (len > 1) { char tmp = *str; *str = *(str + len - 1); *(str + len - 1) = '\0'; reverse_string(str + 1); *(str + len - 1) = tmp; } } int main() { char arr[] = "abcdefjh"; printf("逆序前:%s\n", arr); reverse_string(arr); printf("逆序後:%s\n", arr); return 0; }
寫一個遞歸函數DigitSum(n),輸入一個非負整數,返回組成它的數字之和
例如,調用DigitSum(1729),則應該返回1 + 7 + 2 + 9,它的和是19
例如輸入:1729,輸出:19
思路:大事化小,個位數的9最容易拿下來
#include<stdio.h> int DigitSum(int n) { if (n > 9) { return DigitSum(n / 10) + n % 10; } else { return n; } } int main() { int num = 1729; int sum = DigitSum(num); printf("%d\n", sum); return 0; }
編寫一個函數實現n的k次方,使用遞歸實現。
#include<stdio.h> double my_pow(int n, int k) { if (k > 0) { return n * my_pow(n, k - 1); } else if (k == 0) { return 1; } else { return 1.0 / my_pow(n, -k); } } int main() { int n = 0; int k = 0; double ret = 0.0; scanf("%d%d", &n, &k); ret = my_pow(n, k); printf("%lf\n", ret); return 0; }
遞歸的擴展:
經典題目:
一、漢諾塔問題
二、青蛙跳臺階問題