對C語言中遞歸算法的深入解析
許多教科書都把計算機階乘和菲波那契數(shù)列用來說明遞歸,非常不幸我們可愛的著名的老潭老師的《C語言程序設(shè)計》一書中就是從階乘的計算開始的函數(shù)遞歸。導致讀過這本經(jīng)書的同學們,看到階乘計算第一個想法就是遞歸。但是在階乘的計算里,遞歸并沒有提供任何優(yōu)越之處。在菲波那契數(shù)列中,它的效率更是低的非??植?。
這里有一個簡單的程序,可用于說明遞歸。程序的目的是把一個整數(shù)從二進制形式轉(zhuǎn)換為可打印的字符形式。例如:給出一個值4267,我們需要依次產(chǎn)生字符‘4',‘2',‘6',和‘7'。就如在printf函數(shù)中使用了%d格式碼,它就會執(zhí)行類似處理。
我們采用的策略是把這個值反復除以10,并打印各個余數(shù)。例如,4267除10的余數(shù)是7,但是我們不能直接打印這個余數(shù)。我們需要打印的是機器字符集中表示數(shù)字‘7'的值。在ASCII碼中,字符‘7'的值是55,所以我們需要在余數(shù)上加上48來獲得正確的字符,但是,使用字符常量而不是整型常量可以提高程序的可移植性?!?'的ASCII碼是48,所以我們用余數(shù)加上‘0',所以有下面的關(guān)系:
‘0'+ 0 =‘0'
‘0'+ 1 =‘1'
‘0'+ 2 =‘2'
...
從這些關(guān)系中,我們很容易看出在余數(shù)上加上‘0'就可以產(chǎn)生對應字符的代碼。接著就打印出余數(shù)。下一步再取商的值,4267/10等于426。然后用這個值重復上述步驟。
這種處理方法存在的唯一問題是它產(chǎn)生的數(shù)字次序正好相反,它們是逆向打印的。所以在我們的程序中使用遞歸來修正這個問題。
我們這個程序中的函數(shù)是遞歸性質(zhì)的,因為它包含了一個對自身的調(diào)用。乍一看,函數(shù)似乎永遠不會終止。當函數(shù)調(diào)用時,它將調(diào)用自身,第2次調(diào)用還將調(diào)用自身,以此類推,似乎永遠調(diào)用下去。這也是我們在剛接觸遞歸時最想不明白的事情。但是,事實上并不會出現(xiàn)這種情況。
這個程序的遞歸實現(xiàn)了某種類型的螺旋狀while循環(huán)。while循環(huán)在循環(huán)體每次執(zhí)行時必須取得某種進展,逐步迫近循環(huán)終止條件。遞歸函數(shù)也是如此,它在每次遞歸調(diào)用后必須越來越接近某種限制條件。當遞歸函數(shù)符合這個限制條件時,它便不在調(diào)用自身。
在程序中,遞歸函數(shù)的限制條件就是變量quotient為零。在每次遞歸調(diào)用之前,我們都把quotient除以10,所以每遞歸調(diào)用一次,它的值就越來越接近零。當它最終變成零時,遞歸便告終止。
/*接受一個整型值(無符號0,把它轉(zhuǎn)換為字符并打印它,前導零被刪除*/
#include <stdio.h>
int binary_to_ascii( unsigned int value)
{
unsigned int quotient;
quotient = value / 10;
if( quotient != 0)
binary_to_ascii( quotient);
putchar ( value % 10 + '0' );
}
遞歸是如何幫助我們以正確的順序打印這些字符呢?下面是這個函數(shù)的工作流程。
1. 將參數(shù)值除以10
2. 如果quotient的值為非零,調(diào)用binary-to-ascii打印quotient當前值的各位數(shù)字
3. 接著,打印步驟1中除法運算的余數(shù)
注意在第2個步驟中,我們需要打印的是quotient當前值的各位數(shù)字。我們所面臨的問題和最初的問題完全相同,只是變量quotient的值變小了。我們用剛剛編寫的函數(shù)(把整數(shù)轉(zhuǎn)換為各個數(shù)字字符并打印出來)來解決這個問題。由于quotient的值越來越小,所以遞歸最終會終止。
一旦你理解了遞歸,閱讀遞歸函數(shù)最容易的方法不是糾纏于它的執(zhí)行過程,而是相信遞歸函數(shù)會順利完成它的任務。如果你的每個步驟正確無誤,你的限制條件設(shè)置正確,并且每次調(diào)用之后更接近限制條件,遞歸函數(shù)總是能正確的完成任務。
但是,為了理解遞歸的工作原理,你需要追蹤遞歸調(diào)用的執(zhí)行過程,所以讓我們來進行這項工作。追蹤一個遞歸函數(shù)的執(zhí)行過程的關(guān)鍵是理解函數(shù)中所聲明的變量是如何存儲的。當函數(shù)被調(diào)用時,它的變量的空間是創(chuàng)建于運行時堆棧上的。以前調(diào)用的函數(shù)的變量扔保留在堆棧上,但他們被新函數(shù)的變量所掩蓋,因此是不能被訪問的。
當遞歸函數(shù)調(diào)用自身時,情況于是如此。每進行一次新的調(diào)用,都將創(chuàng)建一批變量,他們將掩蓋遞歸函數(shù)前一次調(diào)用所創(chuàng)建的變量。當我追蹤一個遞歸函數(shù)的執(zhí)行過程時,必須把分數(shù)不同次調(diào)用的變量區(qū)分開來,以避免混淆。
程序中的函數(shù)有兩個變量:參數(shù)value和局部變量quotient。下面的一些圖顯示了堆棧的狀態(tài),當前可以訪問的變量位于棧頂。所有其他調(diào)用的變量飾以灰色的陰影,表示他們不能被當前正在執(zhí)行的函數(shù)訪問。
假定我們以4267這個值調(diào)用遞歸函數(shù)。當函數(shù)剛開始執(zhí)行時,堆棧的內(nèi)容如下圖所示:
不算遞歸調(diào)用語句本身,到目前為止所執(zhí)行的語句只是除法運算以及對quotient的值進行測試。由于遞歸調(diào)用這些語句重復執(zhí)行,所以它的效果類似循環(huán):當quotient的值非零時,把它的值作為初始值重新開始循環(huán)。但是,遞歸調(diào)用將會保存一些信息(這點與循環(huán)不同),也就好是保存在堆棧中的變量值。這些信息很快就會變得非常重要。
現(xiàn)在quotient的值變成了零,遞歸函數(shù)便不再調(diào)用自身,而是開始打印輸出。然后函數(shù)返回,并開始銷毀堆棧上的變量值。
每次調(diào)用putchar得到變量value的最后一個數(shù)字,方法是對value進行模10取余運算,其結(jié)果是一個0到9之間的整數(shù)。把它與字符常量‘0'相加,其結(jié)果便是對應于這個數(shù)字的ASCII字符,然后把這個字符打印出來。
輸出4:
接著函數(shù)返回,它的變量從堆棧中銷毀。接著,遞歸函數(shù)的前一次調(diào)用重新繼續(xù)執(zhí)行,她所使用的是自己的變量,他們現(xiàn)在位于堆棧的頂部。因為它的value值是42,所以調(diào)用putchar后打印出來的數(shù)字是2。
接著遞歸函數(shù)的這次調(diào)用也返回,它的變量也被銷毀,此時位于堆棧頂部的是遞歸函數(shù)再前一次調(diào)用的變量。遞歸調(diào)用從這個位置繼續(xù)執(zhí)行,這次打印的數(shù)字是6。在這次調(diào)用返回之前,堆棧的內(nèi)容如下:
現(xiàn)在我們已經(jīng)展開了整個遞歸過程,并回到該函數(shù)最初的調(diào)用。這次調(diào)用打印出數(shù)字7,也就是它的value參數(shù)除10的余數(shù)。
如果你把打印出來的字符一個接一個排在一起,出現(xiàn)在打印機或屏幕上,你將看到正確的值:4267
使用遞歸一定要有跳出的條件:
這是一個最簡單的遞歸, 不過它會一直執(zhí)行, 可用 Ctrl+C 終止.
#include <stdio.h>
void prn(int num) {
printf("%d/n", num);
if (num > 0) prn(--num);
}
int main(void)
{
prn(9);
getchar();
return 0;
}
實例: 翻轉(zhuǎn)字符串
#include <stdio.h>
void revers(char *cs);
int main(void)
{
revers("123456789");
getchar();
return 0;
}
void revers(char *cs)
{
if (*cs)
{
revers(cs + 1);
putchar(*cs);
}
}
實例: 階乘
#include <stdio.h>
int factorial(int num);
int main(void)
{
int i;
for (i = 1; i <= 9; i++)
printf("%d: %d/n", i, factorial(i));
getchar();
return 0;
}
int factorial(int num)
{
if (num == 1)
return(1);
else
return(num * factorial(num-1));
}
實例: 整數(shù)到二進制
#include <stdio.h>
void IntToBinary(unsigned num);
int main(void)
{
IntToBinary(255); /* 11111111 */
getchar();
return 0;
}
void IntToBinary(unsigned num) {
int i = num % 2;
if (num > 1) IntToBinary(num / 2);
putchar(i ? '1' : '0');
// putchar('0' + i); /* 可代替上面一句 */
}
剖析遞歸:
#include <stdio.h>
void prn(unsigned n);
int main(void)
{
prn(1);
getchar();
return 0;
}
void prn(unsigned n) {
printf("%d: %p/n", n, &n); /* A */
if (n < 4)
prn(n+1); /* B */
printf("%d: %p/n", n, &n); /* C */
}
例輸出效果圖:
分析:
程序運行到 A, 輸出了第一行.
此時 n=1, 滿足 < 4 的條件, 繼續(xù)執(zhí)行 B 開始了自調(diào)用(接著會輸出第二行); 注意 n=1 時語句 C 還有待執(zhí)行.
...如此循環(huán), 一直到 n=4, A 可以執(zhí)行, 但因不滿足條件 B 執(zhí)行不了了; 終于在 n=4 時得以執(zhí)行 C.
但此時內(nèi)存中有四個函數(shù)都等待返回(分別是 n=1、2、3、4 時), 咱們分別叫它 f1、f2、f3、f4.
f4 執(zhí)行 C 輸出了第五行, 函數(shù)返回, 返回給 f3(此時 n=3), f3 得以繼續(xù)執(zhí)行 C, 輸出了第六行.
f3 -> f2 -> 繼續(xù) C, 輸出了第七行.
f2 -> f1 -> 繼續(xù) C, 輸出了第八行, 執(zhí)行完畢!
如此看來, 遞歸函數(shù)還是很費內(nèi)存的(有時不如直接使用循環(huán)), 但的確很巧妙.
欄 目:C語言
下一篇:C語言實現(xiàn)修改文本文件中特定行的實現(xiàn)代碼
本文標題:對C語言中遞歸算法的深入解析
本文地址:http://mengdiqiu.com.cn/a1/Cyuyan/4334.html
您可能感興趣的文章
- 04-02c語言函數(shù)調(diào)用后清空內(nèi)存 c語言調(diào)用函數(shù)刪除字符
- 04-02c語言的正則匹配函數(shù) c語言正則表達式函數(shù)庫
- 04-02func函數(shù)+在C語言 func函數(shù)在c語言中
- 04-02c語言中對數(shù)函數(shù)的表達式 c語言中對數(shù)怎么表達
- 04-02c語言用函數(shù)寫分段 用c語言表示分段函數(shù)
- 04-02c語言編寫函數(shù)冒泡排序 c語言冒泡排序法函數(shù)
- 04-02c語言沒有round函數(shù) round c語言
- 04-02c語言分段函數(shù)怎么求 用c語言求分段函數(shù)
- 04-02C語言中怎么打出三角函數(shù) c語言中怎么打出三角函數(shù)的值
- 04-02c語言調(diào)用函數(shù)求fibo C語言調(diào)用函數(shù)求階乘


閱讀排行
本欄相關(guān)
- 04-02c語言函數(shù)調(diào)用后清空內(nèi)存 c語言調(diào)用
- 04-02func函數(shù)+在C語言 func函數(shù)在c語言中
- 04-02c語言的正則匹配函數(shù) c語言正則表達
- 04-02c語言用函數(shù)寫分段 用c語言表示分段
- 04-02c語言中對數(shù)函數(shù)的表達式 c語言中對
- 04-02c語言編寫函數(shù)冒泡排序 c語言冒泡排
- 04-02c語言沒有round函數(shù) round c語言
- 04-02c語言分段函數(shù)怎么求 用c語言求分段
- 04-02C語言中怎么打出三角函數(shù) c語言中怎
- 04-02c語言調(diào)用函數(shù)求fibo C語言調(diào)用函數(shù)求
隨機閱讀
- 01-10使用C語言求解撲克牌的順子及n個骰子
- 08-05DEDE織夢data目錄下的sessions文件夾有什
- 01-11ajax實現(xiàn)頁面的局部加載
- 04-02jquery與jsp,用jquery
- 01-11Mac OSX 打開原生自帶讀寫NTFS功能(圖文
- 01-10C#中split用法實例總結(jié)
- 08-05織夢dedecms什么時候用欄目交叉功能?
- 01-10delphi制作wav文件的方法
- 08-05dedecms(織夢)副欄目數(shù)量限制代碼修改
- 01-10SublimeText編譯C開發(fā)環(huán)境設(shè)置