C语言函数针对训练--递归篇(动画讲解,由易到难递归例题)
CSDN話題挑戰賽第2期
參賽話題:學習筆記
前言
💖作者:龜龜不斷向前
?簡介:寧愿做一只不停跑的慢烏龜,也不想當一只三分鐘熱度的兔子。
👻專欄:C++初階知識點
👻工具分享:
如果覺得文章對你有幫助的話,還請點贊,關注,收藏支持博主🙊,如有不足還請指點,博主及時改正
函數遞歸
文章目錄
- 函數遞歸
- 🚀1.遞歸的概念
- 🍉史上最簡單的遞歸
- 🚀2.遞歸的必要條件
- 🍉舉例:遞歸設計循環輸出數的每一位
- 🍉遞歸展開圖分析遞歸
- 🚀3.初學遞歸必做練習題
- 🍉遞歸實現strlen
- 🍉遞歸求n的階乘
- 🍉求第n個斐波那契數
- 🍉遞歸實現計算n的k次方
- 🍉遞歸實現逆置字符串
🚀1.遞歸的概念
??上次我們介紹了函數的嵌套調用,一個函數的定義中除了可以調用另一個函數,還可以調用其本身。程序調用自身的編程技巧稱為遞歸( recursion)。遞歸做為一種算法在程序設計語言中廣泛應用。 一個過程或函數在其定義或說明中有直接或間接調用自身的一種方法,它通常把一個大型復雜的問題層層轉化為一個與原問題相似的規模較小的問題來求解,遞歸策略只需少量的程序就可描述出解題過程所需要的多次重復計算,大大地減少了程序的代碼量。
遞歸的主要思考方式在于:把大事化小
?
🍉史上最簡單的遞歸
#include<stdio.h>int main() {printf("hello world\n");main();return 0; }??雖然說是遞歸,但其實是一個死遞歸。就像一個循環設計成死循環一樣,是沒有意義的。main函數調用main函數,該程序會一直輸出hello world,直到程序掛掉(棧溢出stack overflow)。
解釋:
?
??咱們調用的時候也會有棧溢出的報錯
?
?
🚀2.遞歸的必要條件
??在學習循環時,循環也有其對應的循環必要條件,這樣才能構成一個功能完整的可控的循環。
相應地,想要形成一個功能完成的可控的遞歸,也有其對應的遞歸必要條件。
- 存在限制條件,當滿足這個限制條件的時候,遞歸便不再繼續。
- 每次遞歸調用之后越來越接近這個限制條件。
?
🍉舉例:遞歸設計循環輸出數的每一位
遞歸的核心:
例如將順序打印1234,給大事化小
順序打印一位數已經是最小子問題,最小子問題直接打印其個位即可
?
#include<stdio.h>void seq_print(int n) {if (n > 9){seq_print(n / 10);}printf("%d ", n % 10); }int main() {seq_print(1234);return 0; }?
🍉遞歸展開圖分析遞歸
遞歸,這個名字取得非常的精簡,顧名思義:遞推,回歸!它即存在一個遞推的過程,也有有一個回歸的過程。
函數嵌套調用的過程:
圖解
?
🚀3.初學遞歸必做練習題
🍉遞歸實現strlen
?
模擬實現strlen,迭代版本(非遞歸版本),以及遞歸版本
非遞歸版本:
#include<stdio.h>int my_strlen(char* str) {int count = 0;while (*str != '\0'){count ++;str++;}return count; }int main() {char str[] = "hello world";int len = my_strlen(str);printf("len = %d\n", len);return 0; }?
?
遞歸版本:
還是那個核心,大事化小,遞歸邏輯和非遞歸的邏輯是類似的,只是思考角度不一樣
如果第一個字符不是\0,那么字符串的長度為1 + 后面部分字符串的長度
abcdef的長度 -> 1 + bcdef的長度
bcdef的長度 -> 1 + cdef的長度
………………
ef的長度 -> 1 + f的長度
f的長度 -> 1 + ""的長度
最小子問題:""(空串)的長度返回0即可
?
#include<stdio.h>int my_strlen(char* str) {if ((*str) == '\0')//最小子問題的處理方式{return 0;}else{return 1 + my_strlen(str + 1);} }int main() {char str[] = "hello world";int len = my_strlen(str);printf("len = %d\n", len);return 0; }?
🍉遞歸求n的階乘
數學上計算n的階乘的公式有
??而我們遞歸所需要的就是遞推公式,它可以現成的將大事化小
#include<stdio.h>int Fac(int n) {if (n < 2){return 1;}else{return n*Fac(n - 1);} }int main() {int num = 0;int ret = 0;scanf("%d", &num);ret = Fac(num);printf("%d\n", ret);return 0; }?
?
🍉求第n個斐波那契數
斐波那契數列:第一個數和第二個數為1,后面的數是前面兩項數字的和,這樣的數的組合就是斐波那契數列
例如:1 1 2 3 5 8 13 21 34 55…
非常明顯:斐波那契數列的定義已經明確了他的遞歸公式:F(N) = F(N-1) + F(N-2)
#include<stdio.h>int Fibonacci(int n) {if (n <= 2){return 1;}else{return Fibonacci(n - 1) + Fibonacci(n - 2);} }//斐波那契數列 1 1 2 3 5 8 13 21 int main() {int n = 0;scanf("%d", &n);printf("%d\n", Fibonacci(n));return 0; }?
??但是很不幸,上述程序是效率很低的,如果計算45!左右時,都要計算老半天,效率非常低。
解釋:
原因是,里面存在著大量的重復計算,比如說你在計算第5個斐波那契數
在計算出F(4)的過程中,其實也會將F(3)給計算出來,可是到了計算右邊的F(3)時候,又重復地計算了一遍F(3)
因為該遞歸計算出來的數,是沒有保存的,當數字越來越大,就存在著大量的重復計算,效率也就自然低了
?
??我們可以看一看,當計算40!的時候,計算了多少次F(3)
int count = 0;int Fibonacci(int n) {if (n == 3){++count;}if (n <= 2){return 1;}else{return Fibonacci(n - 1) + Fibonacci(n - 2);} }int main() {int n = 0;scanf("%d", &n);printf("%d! = %d\n", n, Fibonacci(n));printf("F(3) = %d\n", count);//printf("fibo(2) = %d\n", count);return 0; }?
?
所以,使用遞歸來實現斐波那契是很低效的,所以咱們要使用迭代。
圖解思路:
![在這里插
#include<stdio.h>int Fibonacci(int n) {if (n <= 2){return 1;}int a = 1;int b = 1;int c = 0;while ((n--) > 2){c = a + b;a = b;b = c;}return c; }//斐波那契數列 1 1 2 3 5 8 13 21 int main() {int n = 0;scanf("%d", &n);printf("%d! = %d\n", n, Fibonacci(n));return 0; }?
??小擴展:,其實大家平時聽到的,青蛙跳臺問題,生兔子問題實質都是斐波那契數列問題
這是劍指offer的題目喲!
#include<stdio.h>int numWays(int n) {size_t left = 1;size_t right = 1;if(n < 2){return 1;}while((n--) >= 2){size_t ret = (left + right)%(1000000007);left = right;right = ret;}return right;}?
🍉遞歸實現計算n的k次方
大家肯定可以想到累乘法–將n累乘k次即可
遞歸思路:n^k = n^(k-1) * k
n的k次方等于n的k-1次放乘以k
#include<stdio.h>double Pow(double n, int k)//n的k次方 {if (k < 0){k = -k;n = 1.0 / n;}if (k == 0){return 1.0;}if (k == 1){return n;}return n * Pow(n, k - 1); }int main() {printf("%f\n", Pow(2, 3));printf("%f\n", Pow(2, -3));return 0; }解釋:
這里需要特殊處理一下k是負數,k是0的情況,如果k是負數,我們要將n變成1/n,k再取正數。
其實,這還是一道劍指offer上面的題,有個大佬想出了一種特別高效的算法,有興趣的同學可以看一下,真的叫人拍案叫絕
?
?
🍉遞歸實現逆置字符串
非遞歸版本:
使用兩個下標來實現左右字符交換即可,字符串的后面是放了\0的,圖中沒畫
#include<stdio.h> #include<string.h>void reverse_string(char str[]) {int left = 0;int right = strlen(str) - 1;while (left < right){char tmp = str[left];str[left] = str[right];str[right] = tmp;++left;--right;} }int main() {char str[] = "abcdef";printf("逆置前\n");printf("%s\n", str);reverse_string(str);printf("逆置后\n");printf("%s\n", str);return 0; }?
?
遞歸版本:
實現邏輯和非遞歸是一致的
思考角度:逆置abcdef -> 交換a和f + 逆置bcde
逆置bcde -> 交換b和e + 逆置cd
逆置cd -> 交換b和e + 逆置""
最小子問題:空串或者串的長度為1,不做任何處理
想必大家都會交換a和f的操作,但是如何實現逆置bcde的操作呢,因為C字符串是以\0結尾的,如果先完成a和f的交換,那么如何取到bcde字符串?方法:a和f的交換先完成一部分:
1 2 5組合起來才是a和f交換的操作,現在將逆置bcde的步驟,插進交換a和f的中,即可實現遞歸
圖解
#include<string.h> #include<stdio.h>void reverse_string(char * string) {int len = strlen(string);if (len > 1){char ch = string[0];//1.將第一個字符保存下來string[0] = string[len - 1];//2.最后一個字符將第一個字符的位置占據string[len - 1] = '\0';//3.最后一個位置給上\0便于操作,能實現遞歸的核心操作reverse_string(string + 1);//4.子問題string[len - 1] = ch;//5.將\0的位置用ch補上} }int main() {char str[] = "abcdef";printf("逆置前\n");printf("%s\n", str);reverse_string(str);printf("逆置后\n");printf("%s\n", str);return 0; }?
??本片文章就講到這里,如果不好理解遞歸的話,建議大家多畫畫遞歸展開圖,對遞歸的遞推和回歸有個一個更深入的理解當然了,遞歸的路還很長,在以后的算法和數據結構中都會遇到,相信不同階段大家會有不同的理解。希望能幫助到大家!
?
??下期遞歸小實戰–實現漢諾塔問題,咱們下期間!
總結
以上是生活随笔為你收集整理的C语言函数针对训练--递归篇(动画讲解,由易到难递归例题)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【数据结构练习题——查找】
- 下一篇: 计算机管理即插即用服务,意外终止Plug