C語言中函數(shù)遞歸的實現(xiàn)示例
前言:
在 C 語言函數(shù)學(xué)習(xí)之旅中,遞歸是一個繞不開的重要話題。它不僅是一種編程技巧,更是一種解決問題的獨特思路。本文將從遞歸的基本概念出發(fā),深入剖析其限制條件,通過實例演示遞歸的應(yīng)用,并對比遞歸與迭代的優(yōu)劣,幫助你徹底掌握 C 語言中的函數(shù)遞歸。
一、遞歸是什么?
遞歸,從字面意義上看,包含 “遞” 和 “歸” 兩層含義。在 C 語言中,遞歸本質(zhì)上是函數(shù)自己調(diào)用自己的一種行為,它將一個大型復(fù)雜的問題層層拆解為與原問題相似但規(guī)模更小的子問題,直到子問題無法再拆分,最終通過子問題的解逐步推導(dǎo)出原問題的解。
1.1遞歸的基本形式
#include <stdio.h>
int main()
{
printf("hehe\n");
main();// main函數(shù)中調(diào)用了main函數(shù)
return 0;
}
這段代碼中,main函數(shù)在自身內(nèi)部調(diào)用了自己,符合于函數(shù)遞歸的定義。但需要注意的是,這段代碼由于缺乏終止條件,最終會陷入死遞歸,導(dǎo)致棧溢出(Stack overflow)的錯誤,出現(xiàn)類似于以下的提示:
“0x7BF20907 (ucrtbased.d11)(test.exe 中) 處有未經(jīng)
處理的異常:0xC0000OP:Stack overflow (參數(shù)
:0x00000000, 0x00602000)”
1.2 遞歸的核心思想
遞歸的核心思想是 “大事化小”。比如要解決一個復(fù)雜問題A,我們可以將其拆解為問題 B,問題 B 的解決方法與問題 A`相似但規(guī)模更??;接著再將問題 B 拆解為問題 C,以此類推,直到拆解出的子問題可以直接解決(即遞歸的終止條件),然后再從子問題的解逐步回歸,得到原問題的解。
二、遞歸的限制條件
并非任意函數(shù)自己調(diào)用自己都能構(gòu)成有效的遞歸,有效的遞歸必須滿足兩個必要條件,這兩個條件缺一不可,否則就會出現(xiàn)死遞歸。
2.1 存在終止條件
遞歸必須存在一個明確的限制條件,當(dāng)滿足這個條件時,遞歸將不再繼續(xù)。這個終止條件是遞歸能夠正常結(jié)束的關(guān)鍵,它就像遞歸的 “剎車”,防止遞歸無限進行下去。
2.2 逐步接近終止條件
在每次遞歸調(diào)用過程中,函數(shù)的參數(shù)或狀態(tài)必須逐漸接近遞歸的終止條件。也就是說,每一次遞歸調(diào)用都應(yīng)該讓問題的規(guī)模更小,使得問題一步步向可直接解決的方向靠近。
三、遞歸實例解析
論結(jié)合實踐才能更好地理解遞歸,下面我們通過兩個經(jīng)典實例,詳細的講解一下遞歸的應(yīng)用過程。
3.1 實例 1:求 n 的階乘
一個正整數(shù)的階乘(factorial)是所有小于及等于該數(shù)的正整數(shù)的積,并且規(guī)定 0 的階乘為 1。自然數(shù) n 的階乘記作 n!,其數(shù)學(xué)公式為:
當(dāng) n = 0 時,n! = 1;
當(dāng) n > 0 時,n! = n × (n - 1) ! 。
問題分析
從公式可以看出,求 n 的階乘可以拆解為求 n 乘以 (n - 1) 的階乘,而求 (n - 1) 的階乘又可以拆解為 (n - 1) 乘以 (n - 2) 的階乘,以此類推,直到拆解到求 0 的階乘(結(jié)果為 1),此時遞歸終止,再逐步回歸計算出原問題的解。
舉例
5 ! = 5* 4* 3* 2 1
4 ! = 4 3* 2* 1
所以 :5 ! = 4 !
代碼實現(xiàn)
#include <stdio.h>
// 定義遞歸函數(shù)計算n的階乘
int Fact(int n)
{
// 遞歸終止條件:n == 0時,返回1
if (n == 0)
return 1;
// 遞歸調(diào)用:n > 0時,返回n乘以(n - 1)的階乘
else
return n * Fact(n - 1);
}
int main()
{
int n = 0;
// 輸入要計算階乘的數(shù)字
scanf("%d", &n);
// 調(diào)用遞歸函數(shù)計算階乘
int ret = Fact(n);
// 輸出結(jié)果
printf("%d\n", ret);
return 0;
}
運行結(jié)果與推演
當(dāng)輸入 n = 5 時,程序運行結(jié)果為 120,符合 5! = 5×4×3×2×1 = 120 的預(yù)期。我們來推演一下遞歸過程:
- Fact(5) = 5 × Fact(4)
- Fact(4) = 4 × Fact(3)
- Fact(3) = 3 × Fact(2)
- Fact(2) = 2 × Fact(1)
- Fact(1) = 1 × Fact(0)
- Fact (0) = 1(遞歸終止)
然后開始回歸計算:
- Fact(1) = 1 × 1 = 1
- Fact(2) = 2 × 1 = 2
- Fact(3) = 3 × 2 = 6
- Fact(4) = 4 × 6 = 24
- Fact(5) = 5 × 24 = 120
3.2 實例 2:順序打印一個整數(shù)的每一位
問題分析
輸入一個整數(shù) m,要求按照順序打印出該整數(shù)的每一位。例如,輸入 1234,輸出 1 2 3 4;輸入 520,輸出 5 2 0。
要解決這個問題,首先需要思考如何獲取整數(shù)的每一位。對于一個多位數(shù),通過取余運算(%10)可以得到其最低位,通過整除運算(/10)可以去掉最低位。但直接使用這種方法得到的數(shù)字順序是倒著的,比如 1234,先得到 4,再得到 3,接著得到 2,最后得到 1。
這時候就可以利用遞歸的思想:要打印 1234 的每一位,可以先打印 123 的每一位,再打印 1234 的最低位 4;要打印 123 的每一位,可以先打印 12 的每一位,再打印 123 的最低位 3;以此類推,直到要打印的數(shù)字是一位數(shù)時,直接打印該數(shù)字,遞歸終止。
代碼實現(xiàn)
#include <stdio.h>
// 定義遞歸函數(shù)順序打印整數(shù)的每一位
void Print(int n)
{
// 遞歸終止條件:當(dāng)n是一位數(shù)時,直接打印
if (n > 9)
{
// 遞歸調(diào)用:先打印n去掉最低位后的數(shù)字的每一位
Print(n / 10);
}
// 打印n的最低位
printf("%d ", n % 10);
}
int main()
{
int m = 0;
// 輸入要打印每一位的整數(shù)
scanf("%d", &m);
// 調(diào)用遞歸函數(shù)打印每一位
Print(m);
return 0;
}
運行結(jié)果與推演
當(dāng)輸入 m = 1234 時,程序運行結(jié)果為 1 2 3 4,符合預(yù)期。
同樣我們可以來推演遞歸過程:
- Print (1234):1234 > 9,調(diào)用 Print (123),之后打印 1234%10 = 4
- Print (123):123 > 9,調(diào)用 Print (12),之后打印 123%10 = 3
- Print (12):12 > 9,調(diào)用 Print (1),之后打印 12%10 = 2
- Print (1):1 <= 9,直接打印 1%10 = 1(遞歸終止)
最終打印順序為 1 2 3 4。
四、遞歸與迭代
遞歸雖然是一種簡潔的編程技巧,但并非在所有場景下都是最優(yōu)選擇,很多時候迭代(通常指循環(huán))是更好的替代方案。下面我們來對比遞歸與迭代的特點,并通過實例分析何時該使用遞歸,何時該使用迭代。
4.1 遞歸的優(yōu)缺點
優(yōu)點
- 代碼簡潔易懂:遞歸能夠直接反映問題的數(shù)學(xué)模型,使得代碼邏輯更加清晰,便于理解和編寫。比如求 n 的階乘,遞歸代碼直接對應(yīng)階乘的數(shù)學(xué)公式,非常直觀。
- 解決復(fù)雜問題更有優(yōu)勢:對于一些復(fù)雜問題,如漢諾塔問題、樹的遍歷等,使用遞歸可以將問題分解為更小的子問題,降低問題的復(fù)雜度,而使用迭代實現(xiàn)則可能非常繁瑣。
缺點
- 棧溢出風(fēng)險:在 C 語言中,每一次函數(shù)調(diào)用都會在棧區(qū)申請一塊棧幀空間來保存局部變量等信息。如果遞歸層次過深,會占用大量的棧空間,導(dǎo)致棧溢出錯誤。
- 效率較低:遞歸過程中存在函數(shù)調(diào)用的開銷,而且可能會出現(xiàn)大量重復(fù)計算的情況,導(dǎo)致程序運行效率降低。
4.2 迭代的優(yōu)缺點
優(yōu)點
- 效率高:迭代通過循環(huán)實現(xiàn),避免了頻繁的函數(shù)調(diào)用開銷,也不會出現(xiàn)重復(fù)計算的問題(除非代碼邏輯設(shè)計不當(dāng)),運行效率更高。
- 內(nèi)存占用少:迭代不需要額外的棧幀空間,內(nèi)存占用相對較少,不存在棧溢出的風(fēng)險。
缺點
代碼邏輯可能復(fù)雜:對于一些復(fù)雜問題,使用迭代實現(xiàn)時,代碼邏輯可能會比較繁瑣,不易理解和維護。
4.3 實例對比:求第 n 個斐波那契數(shù)
斐波那契數(shù)的數(shù)學(xué)定義為:
當(dāng) n <= 2 時,F(xiàn)ib (n) = 1;
當(dāng) n > 2 時,F(xiàn)ib (n) = Fib (n - 1) + Fib (n - 2)。
根據(jù)這個定義,我們能很容易寫出遞歸代碼:
#include <stdio.h>
int Fib(int n)
{
if (n <= 2)
return 1;
else
return Fib(n - 1) + Fib(n - 2);
}
int main()
{
int n = 0;
scanf("%d", &n);
int ret = Fib(n);
printf("%d\n", ret);
return 0;
}
但當(dāng) n = 50 時,程序需要很長時間才能算出結(jié)果,效率極低。這是因為遞歸過程中存在大量重復(fù)計算,我們通過一個計數(shù)器來統(tǒng)計第 3 個斐波那契數(shù)的計算次數(shù):
#include <stdio.h>
int count = 0;
int Fib(int n)
{
if (n == 3)
count++;// 統(tǒng)計第3個斐波那契數(shù)被計算的次數(shù)
if (n <= 2)
return 1;
else
return Fib(n - 1) + Fib(n - 2);
}
int main()
{
int n = 0;
scanf("%d", &n);
int ret = Fib(n);
printf("%d\n", ret);
printf("count = %d\n", count);
return 0;
}
運行結(jié)果
40
102334155count = 39088169
當(dāng)輸入 n = 40 時,輸出結(jié)果中 count = 39088169,這意味著第 3 個斐波那契數(shù)被重復(fù)計算了近 4000 萬次,大量的重復(fù)計算嚴重影響了程序效率。
4.4 迭代實現(xiàn)的優(yōu)勢
針對斐波那契數(shù)的計算,我們可以使用迭代的方式來避免重復(fù)計算。由于斐波那契數(shù)的前兩個數(shù)都是 1,從第三個數(shù)開始,每個數(shù)都是前兩個數(shù)的和.
因此我們可以通過循環(huán)從前往后依次計算:
#include <stdio.h>
int Fib(int n)
{
// 初始化前兩個斐波那契數(shù)
int a = 1;
int b = 1;
// 存儲當(dāng)前斐波那契數(shù),初始值為1(當(dāng)n <= 2時)
int c = 1;
// 循環(huán)計算從第3個到第n個斐波那契數(shù)
while (n > 2)
{
c = a + b;
a = b;
b = c;
n--;
}
return c;
}
int main()
{
int n = 0;
scanf("%d", &n);
int ret = Fib(n);
printf("%d\n", ret);
return 0;
}
使用迭代實現(xiàn)后,即使 n = 100,程序也能快速計算出結(jié)果,效率遠高于遞歸實現(xiàn)。
4.5 遞歸與迭代的選擇建議
- 當(dāng)問題的遞歸實現(xiàn)簡潔易懂,且遞歸層次不深,不存在大量重復(fù)計算時,可以選擇遞歸。例如求 n 的階乘、順序打印整數(shù)的每一位等。
- 當(dāng)遞歸層次較深,或者存在大量重復(fù)計算時,應(yīng)優(yōu)先選擇迭代。例如求第 n 個斐波那契數(shù)。
- 對于一些復(fù)雜問題,如漢諾塔問題、樹的遍歷等,遞歸實現(xiàn)具有明顯優(yōu)勢,此時可以選擇遞歸。
到此這篇關(guān)于C語言中函數(shù)遞歸的實現(xiàn)示例的文章就介紹到這了,更多相關(guān)C語言 函數(shù)遞歸內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C++實現(xiàn)LeetCode(40.組合之和之二)
這篇文章主要介紹了C++實現(xiàn)LeetCode(40.組合之和之二),本篇文章通過簡要的案例,講解了該項技術(shù)的了解與使用,以下就是詳細內(nèi)容,需要的朋友可以參考下2021-07-07
C語言數(shù)據(jù)結(jié)構(gòu)創(chuàng)建及遍歷十字鏈表
這篇文章主要介紹了C語言數(shù)據(jù)結(jié)構(gòu)十字鏈表的創(chuàng)建及遍歷,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步早日升職加薪2021-10-10
c語言鏈表基本操作(帶有創(chuàng)建鏈表 刪除 打印 插入)
這篇文章主要介紹了c語言鏈表基本操作,大家參考使用吧2013-12-12
C/C++?Qt?給ListWidget組件增加右鍵菜單功能
本篇文章給大家介紹ListWidget組件增加一個右鍵菜單,當(dāng)用戶在ListWidget組件中的任意一個子項下右鍵,我們讓其彈出這個菜單,并根據(jù)選擇提供不同的功能,感興趣的朋友跟隨小編一起看看吧2021-11-11

