C語言函數遞歸

文章目錄

  • 一、遞歸
    • 1.遞歸的概念
    • 2.遞歸的思想
    • 3.遞歸的限制條件
  • 二、遞歸的一些典型例子
    • 1.求一個數的階乘
    • 2.順序打印一個整數的每一位
    • 3.漢諾塔
    • 4.青蛙跳臺階
    • 5斐波那契數列
      • 遞歸和迭代的對比

一、遞歸

1.遞歸的概念

遞歸是學習C語言函數繞不開的一個話題,那什么是遞歸呢? 遞歸其實是一種解決問題的方法。在C語言中,遞歸就是函數自己調用自己

2.遞歸的思想

遞歸就是:把一個大型復雜的問題層層轉化為一個與原問題相似的,但規模較小的子問題來求解。直到子問題不能再被拆分,遞歸就結束了。所以遞歸的思想就是把大事化小的過程。遞歸中的就是遞推的意思,就是回歸的意思。

3.遞歸的限制條件

遞歸在書寫的時候,有2個必要條件:
遞歸存在限制條件,當滿足這個限制條件的時候,遞歸便不再繼續。
每次遞歸調用之后越來越接近這個限制條件。

二、遞歸的一些典型例子

1.求一個數的階乘

#include <stdio.h>
int fac(int n)
{if (n == 1||n == 0)return 1;elsereturn n * fac(n-1);
}
int main()
{int n = 0;scanf("%d", &n);int ret = fac(n);printf("%d的階乘是%d\n",n,ret);return 0;
}

運行結果:
在這里插入圖片描述在這里插入圖片描述上面通過函數遞歸來求一個正整數的階乘,那具體要怎么理解上面的代碼呢?分析:一個正整數的階乘(factorial)是所有小于及等于該數的正整數的積,即n的階乘公式為 n=n*(n-1)! 并且0的階乘為1。自然數n的階乘寫作 n!。例如:

5! = 5*4*3*2*1
4! = 4*3*2*1
所以 5! = 5*4!

這樣的思路就是把一個較大的問題,轉換為一個與原問題相似,但規模較小的問題來求解的。當n==1或者n==0的時候,n的階乘是1,其余n的階乘都是可以通過公式計算。
在這里插入圖片描述那我們就可以寫出函數fac求n的階乘,假設fac(n)就是求n的階乘,那么fac(n-1)就是求n-1的階乘。所以構造的函數就是上面的fac。
在這里插入圖片描述就是因為遞歸存在限制條件,這就使得函數自己調用自己時,會有結束的時候。當一個復雜的問題被拆解到不能再拆解的子問題時,我們一眼就能看出子問題的答案。而求解出子問題的答案后,我們就能逐一求解上一層的子問題,以至于就能求解出原問題的答案了。

2.順序打印一個整數的每一位

輸入一個整數n,按照順序打印整數的每一位。比如:
1.輸入: 1234,輸出:1 2 3 4
2.輸入: 520,輸出:5 2 0

#include <stdio.h>
void Print(int n)
{if (n > 9)Print(n / 10);printf("%d ", n % 10);
}
int main()
{int n = 0;printf("請輸入一個整數:");scanf("%d", &n);Print(n);return 0;
}

運行結果:
在這里插入圖片描述在這里插入圖片描述這里需要知道一個知識點:在C語言中,整數除以一個整數,得到的還是整數,因為小數部分被丟棄掉了。
①在C語言中,一個整數除以10以后,這個整數的個位會被去掉,得到個位前面的整數部分。比如:327/10得到結果是32,去掉了個位上的7。一個整數除以10以后,位數會減少一位。
②一個整數模(%)上10以后得到的是個位上的數字,比如:43%10得到的結果為3,即余數就為3。

那怎么理解上面的代碼呢?
在這里插入圖片描述對于這個函數,如果傳進去的實參為1234,進入函數就先判斷n是否大于9,如果n大于9,就將n/10傳給Print函數,這里就又調用了Print函數,直到n的值小于等于9以后,if語句不成立了,就執行printf("%d " , n%10)這條語句,即先打印1234的最高位1,然后返回上一層繼續打印百位的2,然后再返回上一層打印十位上的3,最后返回第一層打印個位上的4。

圖解釋:
在這里插入圖片描述

3.漢諾塔

先學習一下什么是漢諾塔(河內塔)? 漢諾塔是一個起源于印度古老傳說的益智游戲,由法國數學家愛德華·盧卡斯于1883年發明。
在這里插入圖片描述漢諾塔的玩法:對于上面的三個木樁,中間的木樁上疊放有圓盤,而且是按照小圓盤在大圓盤上方的次序疊放的。玩法是將一個木樁上的圓盤移動到另一個木樁上。

移動規則:
1.一次只能移動一個圓盤
2.每個木樁上只有最頂層的圓盤可以移動,并且所移動的圓盤只能移動到空木樁上或者是它要比木樁頂層已存在的圓盤小。也就是說,你每移動一次圓盤,不管在哪根木樁上都要保證小圓盤在大圓盤的上方。

我們從最簡單的情況開始逐一講解:
Ⅰ.如果木樁上只有一個圓盤的時候,要把A柱上的圓盤移動到C柱上,直接拿過去就好了。A→C
在這里插入圖片描述
Ⅱ.如果A木樁上有兩個圓盤,現在要把A木樁上的圓盤移動到C木樁上,就需要借助B樁移動三次圓盤。
在這里插入圖片描述共需三步:A→B,A→C,B→C
在這里插入圖片描述在這里插入圖片描述在這里插入圖片描述
Ⅲ.如果A木樁上有三個圓盤,現在要把A木樁上的圓盤移動到C木樁上最少需要移動多少次圓盤呢?
在這里插入圖片描述答案是最少需要7步:A→C,A→B,C→B,A→C,B→A,B→C,A→C
在這里插入圖片描述在這里插入圖片描述在這里插入圖片描述在這里插入圖片描述到第四步完以后發現,最大的那個圓盤已經放在C柱上了,剩下的兩個圓盤要放到C柱上,其實就和上面只有兩個圓盤的情況是一樣的了,只是這里要借助A木樁移動到C木樁上,位置不一樣,但是移動的次數和兩個圓盤情況下是一樣的。上面的4步加上只有兩個圓盤情況下的3步,最少只需要7步就能把A柱上的圓盤移動到C柱上。

其實通過上面的1~3層圓盤的漢諾塔移動情況的分析,不難發現這里就有遞歸的思想。像只有兩個圓盤的情況:我們要把A柱上最大的那個圓盤先移動到C柱上,就要先把A柱上較小的圓盤先轉移到B柱上,然后才能把較大的那個圓盤移動到C柱上。然后你會發現剩下的那個較小的圓盤要移動到C柱上,就跟柱子上只有一個圓盤的情況是一樣的,只需要移動一步即可。同理:A柱上如果有三個圓盤的情況,當把最大的那個圓盤移動到了C柱上以后,剩下的兩個較小圓盤要移動到C柱上,移動的次數就跟柱子上只有兩個圓盤的情況是一樣的,只是借助的柱子不一樣而已。那代碼要怎么實現呢?

#include <stdio.h>
//1.構造一個用來打印移動軌跡的函數
void Print_Movetrack(char ori, char des)
{static int time = 0;//定義一個靜態變量,用來記錄移動的次數printf("第%d步:%c-→%c\n", ++time, ori, des);
}
//2.漢諾塔代碼的遞歸邏輯
void Hanoi(int n, char a, char b, char c)
{if (n == 1){Print_Movetrack(a, c);}else{Hanoi(n - 1, a, c, b);//Ⅰ將A柱上最大圓盤上方的n-1個圓盤借助C柱移動到B柱上Print_Movetrack(a, c);//Ⅱ將最大的圓盤從A柱移動到C柱上Hanoi(n - 1, b, a, c);//Ⅲ將剛才移動到B柱上的圓盤借助A柱移動到C柱上}
}
int main()
{int n = 0;printf("請輸入圓盤個數:");scanf("%d", &n);Hanoi(n, 'A', 'B', 'C');//n是圓盤個數;A,B,C是三根木樁的編號return 0;
}

運行結果:
在這里插入圖片描述在這里插入圖片描述最難理解的是這個函數是怎么構造出來的,遞歸的邏輯是什么?但是也緊扣遞歸的限制條件,函數里面有使這個遞歸結束的限制條件,每一次遞歸,都會越來越接近這個限制條件。
在這里插入圖片描述我們以A柱上有3個圓盤的情況來說明上面代碼的遞歸邏輯:函數在逐層調用自己時,當滿足限制條件后,會逐一返回上一層繼續執行上一層后續的代碼。
在這里插入圖片描述

4.青蛙跳臺階

青蛙跳臺階問題是啥?這個問題描述的是:一只青蛙去跳臺階,它一次可以跳1個臺階,一次也可以跳2個臺階。
在這里插入圖片描述問:如果現在有n層臺階,青蛙要跳上這n層臺階,共有多少種跳法?
分析:
Ⅰ.當只有一層臺階(n=1)的時候,青蛙只能跳1個臺階,只能跳一次。如圖所示:
在這里插入圖片描述Ⅱ.當有兩層臺階(n=2)的時候,青蛙要跳上這兩層臺階,共有2種跳法。一是每次跳1個臺階,共兩次跳完;二是一次跳2個臺階,一次就跳完。如下圖:
在這里插入圖片描述Ⅲ.當有三層臺階(n=3)的時候,要怎么算有多少種跳法呢?首先分析一下:青蛙一開始一次,要么跳1個臺階,要么跳2個臺階。
①如果青蛙一開始一次跳了1個臺階,那么就還剩下兩層臺階,那這兩層臺階的跳法,就跟上面只有兩層臺階(n=2)的跳法是一樣的,共有2種。
②如果青蛙一開始一次跳了2個臺階,那么就還剩下一層臺階,這一層臺階的跳法就跟只有一層臺階(n=1)的跳法一樣,只有一種。
所以綜上所述,三層臺階的跳法總共有3種。就等于1層臺階和2層臺階的跳法總和。如下圖所示:
在這里插入圖片描述Ⅳ.當有四層臺階(n=4)的時候,也是看青蛙一開始是怎么跳的,如果一開始青蛙跳了1個臺階,那后面就還剩三層臺階,就跟上面只有三層臺階(n=3)的跳法是一樣,有3種跳法。如果一開始青蛙跳了2個臺階,就還剩下兩層臺階,跟上面只有兩層臺階(n=2)的跳法一樣,有2種跳法。所以對于四層臺階,總共有5種跳法。就等于2層臺階和3層臺階的跳法總和。
Ⅴ.依次類推,如果青蛙要跳上n層臺階,這n層臺階的跳法就等于(n-2)層臺階和(n-1)層臺階的跳法總和。規律就是:
在這里插入圖片描述C語言代碼的實現:

#include <stdio.h>
int Step(int n)
{if (n == 1)return 1;else if (n == 2)return 2;elsereturn Step(n - 2) + Step(n - 1);
}
int main()
{int step_num = 0;printf("請輸入臺階層數:");scanf("%d", &step_num);printf("%d層臺階共有%d種跳法\n",step_num , Step(step_num));
}

運行結果:
在這里插入圖片描述在這里插入圖片描述我們把逐層臺階的跳法數量整理出來看:
在這里插入圖片描述通過上面的表也可以直觀的看出來,青蛙跳臺階的種數算法怎么計算,第n層臺階跳法就等于n-2層臺階和n-1層臺階的跳法總和。

5斐波那契數列

先介紹一下斐波那契數列:
斐波那契數列又稱黃金分割數列,是由意大利數學家萊昂納多·斐波那契以兔子繁殖為例而引入的,故稱為兔子數列。這個問題是:兔子在出生兩個月以后,就有繁殖能力了,成熟以后的一對兔子每個月都能生出一對小兔子,如果所有的兔子都不死,那么問在一年以后可以繁殖多少對兔子?

我們拿一對剛剛出生的兔子來分析:
①剛出生的一對小兔子第一個月還沒有繁殖能力,所以第一個月是一對兔子。
②兩個月以后生下一對小兔子,所以現在共有兩對小兔子。
③第三個月后,老兔子又生下一對兔子,上個月新生的那一對兔子還沒有繁殖能力,所以現在一共有3對兔子。
④第四個月后,老兔子繼續生下一對小兔子,剛剛成熟的一對兔子也生下一對小兔子,加上老兔子上個月剛出生的一對兔子現在一共就有5對兔子。

畫出過程圖來說明:
在這里插入圖片描述上圖經過月份那里有一個0,你可以理解為兔子剛出生的時刻。注意圖中寫的是經過的月份,不是實際月份,不要理解錯誤。所以通過上圖可以直觀的看出兔子繁殖對數的規律:
在這里插入圖片描述由表可以看出從第三月起,每個月的兔子對數是前兩個月的兔子對數之和。由此給出斐波那契數列的定義:一個數列從第三項起,每一項都等于前兩項之和,即1 1 2 3 5 8 13 21…這樣一個數列。那遞歸的邏輯在這里就可以理解了,那求第n個斐波那契數的代碼實現:

#include <stdio.h>
//斐波那契數的遞歸邏輯
int Fib(int n)
{if (n == 1 || n == 2)return 1;elsereturn Fib(n - 2) + Fib(n - 1);}
int main()
{int n = 0; //這個n表示的是斐波那契數列中的第幾項scanf("%d", &n);printf("第%d項斐波那契數為%d\n", n , Fib(n));return 0;
}

運行結果:
在這里插入圖片描述所以在第12個月的時候(還沒有經過十二月),總共有144對兔子,即288只兔子。如果是一年以后,那就要經過十二月,到下一年的一月開頭,那就有233對兔子,即有466只兔子。

遞歸和迭代的對比

上面的青蛙跳臺階和斐波那契數列是非常相似的,但是對于斐波那契數列是不適合使用遞歸的方法來實現。當我們要求的斐波那契數列的項數很大時,比如我們要求第50項斐波那契數時,這個遞歸所花費的時間是非常長的,為什么呢?看下面的遞歸邏輯圖:
在這里插入圖片描述從上圖可以看出,遞歸程序會不斷的展開,在展開的過程中,我們很容易就能發現,在遞歸的過程中會有重復計算,而且遞歸層次越深,冗余計算就會越多。如果采用非遞歸的方式(迭代),效率就可以大大提高:

#include <stdio.h>
int Fib(int n)
{int a = 1;int b = 1;int c = 1;while (n > 2){c = a + b;a = b;b = c;n--;}return c;
}
int main()
{int n = 0;while (scanf("%d", &n) != EOF){int ret = Fib(n);printf("第%d項斐波那契數為%d\n", n, ret);}return 0;
}

運行結果:
在這里插入圖片描述上面的這種求第幾項斐波那契數的方法就是迭代法。每一次對過程的重復稱為一次"迭代",而每一次迭代得到的結果將會作為下一次迭代的初始值。這種就叫做迭代法,也稱為輾轉法。所以不是所有的問題都適合用遞歸的方法。

Ⅰ.遞歸的優缺點
優點:
可以將大問題轉化為小問題,減少代碼量。
可以去掉不斷重復的代碼,使代碼精簡,提升可讀性。
缺點:
遞歸調用浪費空間,遞歸太深還容易造成堆棧溢出。

Ⅱ.迭代的優缺點
優點:
可以將重復的問題轉化為一單問題的重復操作,減少代碼量。
代碼運行效率高,時間只因為循環次數的增加而增加,沒有額外的內存空間開銷。
缺點:
代碼不如遞歸簡潔,有時可能不容易理解。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/14524.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/14524.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/14524.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【算法刷題day56】 Leetcode:647. 回文子串、516. 最長回文子序列

文章目錄 Leetcode 647. 回文子串解題思路代碼總結 Leetcode 516. 最長回文子序列解題思路代碼總結 草稿圖網站 java的Deque Leetcode 647. 回文子串 題目&#xff1a;647. 回文子串 解析&#xff1a;代碼隨想錄解析 解題思路 斜上三角&#xff0c;從左下往上遍歷&#xff0c…

【代碼隨想錄】動態規劃之完全背包問題與打家劫舍問題

前言 更詳細的在大佬的代碼隨想錄 (programmercarl.com) 本系列僅是簡潔版筆記&#xff0c;為了之后方便觀看 完全背包 for(int i 0; i < weight.size(); i) { // 遍歷物品for(int j weight[i]; j < bagWeight; j) { // 遍歷背包容量dp[j] max(dp[j], dp[j - weigh…

ElementPlus Steps步驟條插槽 v-slot:title

<el-steps finish-status"success"><el-stepv-for"item in uniqueReverseArr":status"item.status 2? success: item.status 3? error: item.status 1? finish: process"click.native"stepClick(item)"><templat…

PyTorch中Tensor簡介

PyTorch中所有的操作都是基于Tensor&#xff08;張量&#xff09;的&#xff0c;因此理解張量的含義并能夠自由創建張量是十分必要的。 張量是PyTorch中最基本的操作對象。我們可以用數學中的概念來輔助理解一下張量&#xff0c;如圖5-1所示。 標量&#xff08;Scalar&#x…

c#數據庫的增刪改查

** 安裝數據庫包 ** 在使用 SQLite 數據庫時&#xff0c;你需要安裝適當的 NuGet 包來提供與 SQLite 的集成。 1.打開 Visual Studio 中的你的項目 2.在頂部菜單欄中選擇 “項目” -> “管理 NuGet 包” 3.在 NuGet 管理器中搜索 “System.Data.SQLite” 4.找到適合你項目…

【openlayers系統學習】1.1渲染GeoJSON,添加link交互

一、渲染GeoJSON 在進入編輯之前&#xff0c;我們將看一下使用矢量源和圖層進行基本要素渲染。Workshop在 data? 目錄中包含一個 countries.json? GeoJSON文件。我們首先加載該數據并將其渲染在地圖上。 首先&#xff0c;編輯 index.html? 以便向地圖添加深色背景&#xf…

Vue 組件的生命周期鉤子有哪些用途是什么

Vue 組件的生命周期鉤子提供了在不同階段執行特定邏輯的機會&#xff0c;這些鉤子在組件的創建、掛載、更新、銷毀等過程中被調用。以下是每個生命周期鉤子的常見用途&#xff1a; beforeCreate 用途&#xff1a;由于在這個階段&#xff0c;組件的 data、computed、methods 和…

使用llama.cpp實現LLM大模型的格式轉換、量化、推理、部署

使用llama.cpp實現LLM大模型的量化、推理、部署 大模型的格式轉換、量化、推理、部署概述克隆和編譯環境準備模型格式轉換GGUF格式bin格式 模型量化模型加載與推理模型API服務模型API服務(第三方)GPU推理 大模型的格式轉換、量化、推理、部署 概述 llama.cpp的主要目標是能夠在…

【代碼隨想錄算法訓練營第37期 第十五天 | LeetCode226.翻轉二叉樹、101.對稱二叉樹 2】

代碼隨想錄算法訓練營第37期 第十五天 | LeetCode226.翻轉二叉樹、101.對稱二叉樹 2 一、226.翻轉二叉樹 解題代碼C&#xff1a; /*** Definition for a binary tree node.* struct TreeNode {* int val;* TreeNode *left;* TreeNode *right;* TreeNode() : …

【軟考中級 軟件設計師】數據結構

數據結構是計算機科學中一個基礎且重要的概念&#xff0c;它研究數據的存儲結構以及在此結構上執行的各種操作。在準備軟考中級-軟件設計師考試時&#xff0c;掌握好數據結構部分對于通過考試至關重要。下面是一些核心知識點概覽&#xff1a; 基本概念&#xff1a; 數據結構定義…

VBA_MF系列技術資料1-615

MF系列VBA技術資料1-615 為了讓廣大學員在VBA編程中有切實可行的思路及有效的提高自己的編程技巧&#xff0c;我參考大量的資料&#xff0c;并結合自己的經驗總結了這份MF系列VBA技術綜合資料&#xff0c;而且開放源碼&#xff08;MF04除外&#xff09;&#xff0c;其中MF01-0…

spring-boot集成slf4j(二)logback配置詳解

一、configuration 根節點&#xff1a;configuration&#xff0c;作為頂級標簽&#xff0c; 可以用來配置一些lockback的全局屬性&#xff0c;常見的屬性如下&#xff1a; &#xff08;1&#xff09;scan“true” &#xff1a;scan是否開啟自動掃描&#xff0c;監控配置文件更…

el-table 組件實現 “合并單元格 + N行數據小計” 功能

目錄 需求 - 要實現的效果初始代碼代碼升級&#xff08;可供多個表格使用&#xff09;CommonTable.vue 子組件 使用子組件1 - 父組件 - 圖1~圖3使用效果展示 使用子組件2 - 父組件 - 圖4使用效果展示 注意【代碼優化 - 解決bug】 需求 - 要實現的效果 父組件中 info 數據示例 …

內網安全之證書服務基礎知識

PKI公鑰基礎設施 PKI(Public Key Infrastructure)公鑰基礎設施&#xff0c;是提供公鑰加密和數字簽名服務的系統或平臺&#xff0c;是一個包括硬件、軟件、人員、策略和規程的集合&#xff0c;用來實現基于公鑰密碼體制的密鑰和證書的產生、管理、存儲、分發和撤銷等功能。企業…

Android Debug Bridge(ADB)命令使用

引言 Android Debug Bridge&#xff08;ADB&#xff09;是一套功能強大的命令行工具&#xff0c;它為Android開發者和高級用戶提供了與Android設備通信的能力。無論是進行應用開發、測試還是執行日常設備管理任務&#xff0c;ADB都是不可或缺的工具。本文將詳細介紹一些常用的…

element-plus:踩坑日記

el-table Q&#xff1a;有fixed屬性時&#xff0c;無數據時&#xff0c;可能出現底部邊框消失的bug 現象&#xff1a; 解決方法&#xff1a; .el-table__empty-block {border-bottom: 1px solid var(--el-table-border-color); } el-collapse 折疊面板 Q&#xff1a;標題上…

云平臺的安全能力提升解決方案

提升云平臺的安全能力是確保數據和服務安全的關鍵步驟。針對大型云平臺所面臨的云上安全建設問題&#xff0c;安全狗提供完整的一站式云安全解決方案&#xff0c;充分匹配云平臺安全管理方的需求和云租戶的安全需求。協助大型云平臺建設全網安全態勢感知、統一風險管理、統一資…

加強堆(大根堆)

way&#xff1a;看上去好像就是加了個indexMap記錄節點在數組heap中的下標&#xff0c;然后就是可以查到某個元素是否在堆里并且可以進行位置的調整&#xff0c;普通的堆是沒法知道元素是不是在的&#xff0c;只能彈堆頂元素&#xff0c;插入到堆尾這樣子。如果覺得heapSize有點…

PCIE協議-4-物理層邏輯模塊

4.1 簡介 物理層將事務層和數據鏈路層與用于鏈路數據交換的信令技術隔離開來。物理層被劃分為邏輯物理層和電氣物理層子模塊&#xff08;見圖4-1&#xff09;。 4.2 邏輯物理層子模塊 邏輯子模塊有兩個主要部分&#xff1a;一個發送部分&#xff0c;它準備從數據鏈路層傳遞過…

Spring 中常用的手動裝載 bean 方法

在 Spring 的 bean 裝載條件中&#xff0c;雖然 Spring 給我們提供了非常好用便捷的 Condition 相關注解&#xff0c;但是很多時候 Condition 相關注解并不滿足我們的需求&#xff0c;我需要更復雜的條件手動控制是否裝置 bean。這個時候我們就可以實現 Spring 為我們提供的幾個…