【C語言】函數(四):函數遞歸與迭代,二者有什么區別

目錄

  • 前言
  • 遞歸
    • 定義
    • 遞歸的兩個必要條件
    • 接受一個整型值(無符號),按照順序打印它的每一位
    • 使用函數不允許創建臨時變量,求字符串“abcd”的長度
    • 求n的階乘
    • 求第n個斐波那契數
  • 迭代
  • 總結
  • 遞歸與迭代的主要區別
    • 用法不同
    • 結構不同
    • 時間開銷不同
  • 兩個經典問題

前言

從前有座山,山里有座廟,廟里有個老和尚,正在給小和尚講故事!故事是什么?“從前有座山,山里有座廟,廟里有個老和尚,正在給小和尚講故事!故事是什么?‘從前有座山,山里有座廟,廟里有個老和尚,正在給小和尚講故事!故事是什么?……’”

遞歸

定義

計算機科學中,遞歸是是指在函數的定義中使用函數自身的方法。它通常把一個大型的復雜問題層層轉化為一個與原問題相似的規模較小的問題來求解,遞歸只需要少量的代碼就可以描述出解題過程中的多次重復計算,大大減少了程序的代碼量。
遞歸的主要思想是:大化小

遞歸的兩個必要條件

1.存在限制條件,當滿足這個限制條件時,遞歸停止。
2.每次遞歸調用之后越來越接近限制條件。

錯誤示例:

#include<stdio.h>int main()
{printf("hello world!\n");main();return 0;
}

在這里插入圖片描述

畫紅線的地方,意思是棧溢出,從上面寫的程序中發現,遞歸沒有停止的限制條件,導致死遞歸。

接受一個整型值(無符號),按照順序打印它的每一位

示例1:
問題描述:
接受一個整型值(無符號),按照順序打印它的每一位。
樣例輸入:1234
樣例輸出:1 2 3 4

代碼示范:

#include<stdio.h>void Func(unsigned int x)
{if (x > 9){Func(x / 10);}printf("%d ", x % 10);
}
int main()
{unsigned int num = 0;scanf("%d", &num); //假設輸入的是123Func(num);return 0;
}

在這里插入圖片描述

到這里對遞歸應該有一個比較清晰的認識了,在圖中紅色過程表示的就是遞歸當中的“遞”,藍色過程表示的就是遞歸當中的“歸”。

使用函數不允許創建臨時變量,求字符串“abcd”的長度

示例2:
問題描述:
使用函數不允許創建臨時變量,求字符串“abcd”的長度
分析: 直接求字符串“abcd”的長度,它是字符串,在前面文章中說過字符串的結束標志是 \0

代碼展示:

#include<stdio.h>
#include<string.h>int Length(char* l)
{int count = 0;while (*l != '\0'){count++;l++;}return count;
}
int main()
{char arr[] = "abcd";int len = Length(arr);printf("%d\n", len);return 0;
}

這段代碼完全沒有問題,但是題目要求不允許創建臨時變量,這里創建了臨時變量count

再分析:
在這里插入圖片描述

所以我們可以將Length函數寫成遞歸的形式:

//遞歸
int Length(char* length)
{if (*length == '\0')return 0;elsereturn 1 + Length(length + 1);
}

我們再分析過程:

在這里插入圖片描述

求n的階乘

我們回顧一下n!怎么算:

#include<stdio.h>int Func(int x)
{int i = 0;int j = 1;for (i = 1; i <= x; i++){j *= i;}return j;
}
int main()
{int n = 0;scanf("%d", &n);int result = Func(n);printf("%d\n", result);return 0;
}

上述代碼是非遞歸的形式,我們再來思考一下如何使用遞歸來寫n!
在這里插入圖片描述

我們可以發現n!=n*(n-1)!
所以Func函數可以寫成:

int Func(int x)
{if (x <= 1)return 1;else return x * Func(x - 1);
}

求第n個斐波那契數

斐波那契數列由01開始,之后的斐波那契數就是由之前的兩數相加而得出。
前幾個斐波那契數是:
1、 1、 2、 3、 5、 8、 13、 21、 34、 55、 89、 144……
也就是說從第三個數開始,后面的每一個斐波那契數都是前兩個數的和。
在這里插入圖片描述

到這里就可以直接寫代碼了:

#include<stdio.h>int Fib(int n)
{if (n <= 2)return 1;elsereturn Fib(n - 1) + Fib(n - 2);
}
int main()
{int n = 0;scanf("%d", &n);int result = Fib(n);printf("%d\n", result);return 0;
}

這時候我們信心滿滿,讓計算機幫我求第50個斐波那契數,當我們執行程序后在鍵盤輸入50,卻發現,等了很久都沒有發現它輸出內容。
為什么?

我們要求第50個斐波那契數,就需要計算第49個和第48個數,計算第49個數又需要計算第48個數和第47個數,可以想一下上面這個圖畫到末端需要畫多少,除了前兩個數,要算2的48次方,而int型只占4個字節的內存,也就是32位,2的32次方都已經42億多,可想而知計算量非常龐大。按照遞歸的方式要進行大量的重復計算。我們可以做一個計數,計算第40個斐波那契數中3被計算了多少次,你會發現3被計算了將近四千萬次,效率非常低。所以不是因為計算機偷懶沒算,它也在拼命地計算,只是量太大,它一會兒也算不出來。

那么應該如何改進呢?

迭代

在計算機科學中,迭代是程序中對一組指令(或一定步驟)的重復。它既可以被用作通用的術語(與“重復”同義),也可以用來描述一種特定形式的具有可變狀態的重復。可以簡單理解為普通循環。但與普通循環有所差別,迭代時,循環代碼中參與運算的變量同時是保存結果的變量,當前保存的結果作為下一次循環計算的初始值

我們知道函數形參是被存放在棧區當中,遞歸每“遞”一次,就要開辟一個變量的內存,那么當參數非常大的時候,棧區內存不夠了,棧區放不下了,也就是說棧區空間已經被耗盡了,但是你的東西還沒放完,這個時候就會出現棧溢出的現象。

那么我們回頭再看求第n個斐波那契數,能否將遞歸轉換成迭代的形式。
在這里插入圖片描述

	while (n >= 3){c = a + b;a = b;b = c;n--;}return c;

那么當n小于3的時候,也就是第1個或者第2個數,都是1,所以在給a,b,c初始化的時候,都賦值為1即可。

完整代碼:

#include<stdio.h>
//迭代
int Fib(int n)
{int a = 1;int b = 1;int c = 1;while (n >= 3){c = a + b;a = b;b = c;n--;}return c;
}
int main()
{int n = 0;scanf("%d", &n);int result = Fib(n);printf("%d\n", result);return 0;
}

這個時候程序運行后輸入50,盡管結果仍然是錯誤的,但是速度非常快。解決了大量重復計算的問題。

總結

當使用遞歸可能會導致棧溢出時,程序效率明顯下降的時候,就不能夠使用遞歸了。
如何解決?
1.可以使用迭代替換遞歸。
2.在遞歸函數設計中可以使用static限制變量,讓變量申請一塊內存后,在那一塊內存折騰。不僅不再大量開辟棧區內存,從而導致棧溢出,并且static可以保存遞歸時的中間狀態,并且為各個調用層所訪問。

  • 遞歸代碼量少,迭代不易想到,遞歸比迭代更清晰。所以許多問題采用遞歸的方式解釋。
  • 迭代實現比遞歸實現的代碼可讀性差,但是效率高。
  • 當問題復雜的時候,難以用迭代實現,此時使用遞歸會更加簡潔。

遞歸與迭代的主要區別

用法不同

  • 迭代是代碼塊的重復。雖然需要更多的代碼,但時間復雜度通常小于遞歸的時間復雜度。
  • 遞歸是多次調用自身,因此代碼長度非常小。但是,當有非常非常多次的遞歸調用時,遞歸的時間復雜度可能會呈指數級增長。

結構不同

  • 迭代是環結構,從初始狀態開始,每次迭代都遍歷這個環,并更新狀態,多次迭代直到到達結束狀態。
  • 遞歸是樹結構,從字面可以理解為重復“遞”和“歸”的過程,當“遞”到達底部時就會開始“歸”。

時間開銷不同

  • 與迭代相比,遞歸具有大量的開銷。遞歸具有重復函數調用的開銷,即由于重復調用同一函數,代碼的時間復雜度增加了許多倍。

兩個經典問題

  • 漢諾塔問題
  • 青蛙跳臺階問題

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

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

相關文章

容器args中使用環境變量

1 背景 有時候需將變量傳給容器&#xff0c;作為命令的參數。比如定義一個branch name&#xff0c;然后在initcontainer中clone對應的配置&#xff0c;進行后續操作。這時候我們就可以通過ConfigMap來保存這個值&#xff0c;然后在Deployment里讀取這個ConfigMap&#xff0c;并…

毛利率創歷史新高,三季度的小米拿出“新王牌”?

近日&#xff0c;小米正式發布了今年三季度的財報。財報數據顯示&#xff0c;小米第三季度經調整凈利潤為59.9億元人民幣&#xff0c;同比增長182.9%&#xff0c;遠超市場預期的48億元。這其中&#xff0c;手機業務作為小米的基本盤一直是市場的關注焦點。今年三季度&#xff0…

Python----函數的參數

在函數定義與調用時&#xff0c;我們可以根據自己的需求來實現參數的傳遞。在Python中&#xff0c;函數的參數一共有兩種形式&#xff1a;① 形參 ② 實參 形參&#xff1a;在函數定義時&#xff0c;所編寫的參數就稱之為形式參數 實參&#xff1a;在函數調用時&#xff0c;所…

vue3的基本使用(超詳細)

一、初識vue3 1.vue3簡介 2020年9月18日&#xff0c;vue3發布3.0版本&#xff0c;代號大海賊時代來臨&#xff0c;One Piece特點&#xff1a; 無需構建步驟&#xff0c;漸進式增強靜態的 HTML在任何頁面中作為 Web Components 嵌入單頁應用 (SPA)全棧 / 服務端渲染 (SSR)Jams…

大表添加字段不停服思路

前言 這個是源自于昨天寫的業務背景&#xff0c;對接蘋果支付退款退單接口-CSDN博客 涉及到了order表的改動&#xff0c;而目前order表已經有2千萬的數據&#xff0c;如果退款字段都直接加在這張表里面可能會比較慢&#xff0c;所以才有這篇文章&#xff0c;文章里只討論思路&a…

搜索引擎語法

演示自定的Google hacking語法&#xff0c;解釋含意以及在滲透過程中的作用 Google hacking site&#xff1a;限制搜索范圍為某一網站&#xff0c;例如&#xff1a;site:baidu.com &#xff0c;可以搜索baidu.com 的一些子域名。 inurl&#xff1a;限制關鍵字出現在網址的某…

重生之我是一名程序員 40 ——字符串函數(1)

哈嘍啊大家晚上好&#xff01;今天呢給大家帶來點新的東西——字符串函數strcpy。 首先&#xff0c;讓我來給大家介紹一下它。strcpy函數是C語言中的一個字符串函數&#xff0c;用于將一個字符串復制到另一個字符串中。其函數原型為&#xff1a; char* strcpy(char* dest, co…

LeetCode無重復字符的最長字符串的Java實現

題目 給定一個字符串 s &#xff0c;請你找出其中不含有重復字符的 最長連續子字符串 的長度。 示例 1: 輸入: s "abcabcbb" 輸出: 3 解釋: 因為無重復字符的最長子字符串是 "abc"&#xff0c;所以其長度為 3。示例 2: 輸入: s "bbbbb" 輸…

【Spring】MyBatis的操作數據庫

目錄 一&#xff0c;準備工作 1.1 創建工程 1.2 準備數據 1.3 數據庫連接字符串 1.4 創建持久層接口UserInfoMapper 1.5 單元測試 二&#xff0c;注解的基礎操作 2.1 打印日志 2.2 參數傳遞 2.3 增&#xff08;Insert&#xff09; 2.4 刪&#xff08;Delete&#x…

插件預熱 | 且看安全小白如何輕松利用Goby插件快速上分

001 前言 各位師傅們好&#xff0c;首先強調一遍我可沒做壞事&#xff0c;我只是想學技術&#xff0c;我有什么壞心思呢 回到正題&#xff0c;作為一個初學者&#xff0c;我想和大家分享一下我是如何利用 Goby 進行刷分的經歷。大家都知道&#xff0c;剛開始學習的時候&…

python每日一題——4移動0

題目 給定一個數組 nums&#xff0c;編寫一個函數將所有 0 移動到數組的末尾&#xff0c;同時保持非零元素的相對順序。 請注意 &#xff0c;必須在不復制數組的情況下原地對數組進行操作。 示例 1: 輸入: nums [0,1,0,3,12] 輸出: [1,3,12,0,0] 示例 2: 輸入: nums [0]…

Go 語言中的 Switch 語句詳解

switch語句 使用switch語句來選擇要執行的多個代碼塊中的一個。 在Go中的switch語句類似于C、C、Java、JavaScript和PHP中的switch語句。不同之處在于它只執行匹配的case&#xff0c;因此不需要使用break語句。 單一case的switch語法 switch 表達式 { case x:// 代碼塊 cas…

web前端開發基礎------外邊距折疊現象

引言 在設置樣式時&#xff0c;需要遵循先整體再細節&#xff0c;先通用樣式再特殊樣式的順序進行設置 一&#xff0c;什么是外邊距折疊現象呢&#xff1f; 外邊距折疊 定義&#xff1a; 外邊距折疊是指相鄰的兩個或者多個外邊距&#xff08;margin&#xff09;在垂直方向會合并…

Python入門學習篇(二)——算術運算符

1 算術運算符 1.1 分類 類型含義示例注意事項加號12?3“12”“3"?"123”數值之間,是加法運算(True為1,False為0)字符串之間,是進行拼接數值和字符串之間是不可以使用加法運算的,會報錯-減號1-2?-1*乘號2*3?6/除法2/1?2.0除法的結果永遠為小數%取余10%2?0//取…

SAP 預付款清賬程序

預付款批量清賬程序&#xff0c;也是來自于網上&#xff0c;稍微改了一下。依據付款參考清賬。 原文參考&#xff1a;【ABAP】供應商、客戶的特殊總賬和非特殊總賬清賬_sap f-44 bapi-CSDN博客 &---------------------------------------------------------------------*…

老生常談 - 從輸入URL到頁面加載的過程(詳細版)

從輸入URL到頁面加載的過程 之前一直都是直接看一下總結的八股文章&#xff0c;對于實際的整個鏈路并不是特別熟悉&#xff0c;這次花了一天多的時間看了很多資料&#xff0c;對于整個頁面加載的流程有了自己的理解&#xff0c;從前端開始訪問的瀏覽器多線程、緩存等問題&#…

5-11一個球從100米自由落下

#include<stdio.h> int main(){double down100;double back down/2;int n;//次數for(n2;n<10;n){downdownback*2;backback/2; }printf("第10次落地經過%f米\n",down);printf("第10次反彈%f米\n",back);return 0;}

href和src的區別

1、請求資源類型不同 &#xff08;1&#xff09; href是Hypertext Reference的縮寫&#xff0c;表示超文本引用。用來建立當前元素和文檔之間的鏈接。常用的有&#xff1a;link、a。 &#xff08;2&#xff09;在請求 src 資源時會將其指向的資源下載并應用到文檔中&#xff0…

分布式事務seata的AT模式介紹

分布式事務seata的AT模式介紹 seata是阿里開源的一款分布式事務解決方案&#xff0c;致力于提供高性能和簡單易用的分布式事務服務。Seata 將為用戶提供了 AT、TCC、SAGA 和 XA 事務模式&#xff0c;本文主要介紹AT模式的使用。 seata安裝 下載seata服務&#xff0c;官方地址…

測試數據不會造?可以用這個工具Faker

在測試過程中&#xff0c;大家應該都遇到過各種各樣的數據構造問題。e.g. 構造一批通訊錄、構造一批用戶三要素(姓名手機號身份證)、構造一批銀行卡數據…… 這時候&#xff0c;測試數據大多數可能是這樣的: 張三, 130 0000 0001 李四, 130 0000 0002 王五, 130 0000 0003 …