遞迴演算法詳細分析 C語言

2021-06-26 16:20:18 字數 3821 閱讀 9056

c通過執行時堆疊支援遞迴函式的實現。遞迴函式就是直接或間接呼叫自身的函式。

許多教科書都把計算機階乘和菲波那契數列用來說明遞迴,非常不幸我們可愛的著名的老潭老師的《c語言程式設計》一書中就是從階乘的計算開始的函式遞迴。導致讀過這本經書的同學們,看到階乘計算第乙個想法就是遞迴。但是在階乘的計算裡,遞迴並沒有提供任何優越之處。在菲波那契數列中,它的效率更是低的非常恐怖。

這裡有乙個簡單的程式,可用於說明遞迴。程式的目的是把乙個整數從二進位制形式轉換為可列印的字元形式。例如:給出乙個值4267,我們需要依次產生字元『4』,『2』,『6』,和『7』。就如在printf函式中使用了%d格式碼,它就會執行類似處理。

我們採用的策略是把這個值反覆除以10,並列印各個餘數。例如,4267除10的餘數是7,但是我們不能直接列印這個餘數。我們需要列印的是機器字符集中表示數字『7』的值。在ascii碼中,字元『7』的值是55,所以我們需要在餘數上加上48來獲得正確的字元,但是,使用字元常量而不是整型常量可以提高程式的可移植性。『0』的ascii碼是48,所以我們用餘數加上『0』,所以有下面的關係:

『0』+ 0 =『0』

『0』+ 1 =『1』

『0』+ 2 =『2』

...從這些關係中,我們很容易看出在餘數上加上『0』就可以產生對應字元的**。接著就列印出餘數。下一步再取商的值,4267/10等於426。然後用這個值重複上述步驟。

這種處理方法存在的唯一問題是它產生的數字次序正好相反,它們是逆向列印的。所以在我們的程式中使用遞迴來修正這個問題。

我們這個程式中的函式是遞迴性質的,因為它包含了乙個對自身的呼叫。乍一看,函式似乎永遠不會終止。當函式呼叫時,它將呼叫自身,第2次呼叫還將呼叫自身,以此類推,似乎永遠呼叫下去。這也是我們在剛接觸遞迴時最想不明白的事情。但是,事實上並不會出現這種情況。

這個程式的遞迴實現了某種型別的螺旋狀while迴圈。while迴圈在迴圈體每次執行時必須取得某種進展,逐步迫近迴圈終止條件。遞迴函式也是如此,它在每次遞迴呼叫後必須越來越接近某種限制條件。當遞迴函式符合這個限制條件時,它便不在呼叫自身

在程式中,遞迴函式的限制條件就是變數quotient為零。在每次遞迴呼叫之前,我們都把quotient除以10,所以每遞迴呼叫一次,它的值就越來越接近零。當它最終變成零時,遞迴便告終止。

/*接受乙個整型值(無符號0,把它轉換為字元並列印它,前導零被刪除*/

#include

int binary_to_ascii( unsigned int value)

遞迴是如何幫助我們以正確的順序列印這些字元呢?下面是這個函式的工作流程。

1. 將引數值除以10

2. 如果quotient的值為非零,呼叫binary-to-ascii列印quotient當前值的各位數字

3. 接著,列印步驟1中除法運算的餘數

注意在第2個步驟中,我們需要列印的是quotient當前值的各位數字。我們所面臨的問題和最初的問題完全相同,只是變數quotient的值變小了。我們用剛剛編寫的函式(把整數轉換為各個數字字元並列印出來)來解決這個問題。由於quotient的值越來越小,所以遞迴最終會終止。

一旦你理解了遞迴,閱讀遞迴函式最容易的方法不是糾纏於它的執行過程,而是相信遞迴函式會順利完成它的任務。如果你的每個步驟正確無誤,你的限制條件設定正確,並且每次呼叫之後更接近限制條件,遞迴函式總是能正確的完成任務。

但是,為了理解遞迴的工作原理,你需要追蹤遞迴呼叫的執行過程,所以讓我們來進行這項工作。追蹤乙個遞迴函式的執行過程的關鍵是理解函式中所宣告的變數是如何儲存的。當函式被呼叫時,它的變數的空間是建立於執行時堆疊上的。以前呼叫的函式的變數扔保留在堆疊上,但他們被新函式的變數所掩蓋,因此是不能被訪問的。

當遞迴函式呼叫自身時,情況於是如此。每進行一次新的呼叫,都將建立一批變數,他們將掩蓋遞迴函式前一次呼叫所建立的變數。當我追蹤乙個遞迴函式的執行過程時,必須把分數不同次呼叫的變數區分開來,以避免混淆。

程式中的函式有兩個變數:引數value和區域性變數quotient。下面的一些圖顯示了堆疊的狀態,當前可以訪問的變數位於棧頂。所有其他呼叫的變數飾以灰色的陰影,表示他們不能被當前正在執行的函式訪問。

假定我們以4267這個值呼叫遞迴函式。當函式剛開始執行時,堆疊的內容如下圖所示:

執行除法之後,堆疊的內容如下:

接著,if語句判斷出quotient的值非零,所以對該函式執行遞迴呼叫。當這個函式第二次被呼叫之初,堆疊的內容如下:

堆疊上建立了一批新的變數,隱藏了前面的那批變數,除非當前這次遞迴呼叫返回,否則他們是不能被訪問的。再次執行除法運算之後,堆疊的內容如下:

quotient的值現在為42,仍然非零,所以需要繼續執行遞迴呼叫,並再建立一批變數。在執行完這次呼叫的出發運算之後,堆疊的內容如下:

此時,quotient的值還是非零,仍然需要執行遞迴呼叫。在執行除法運算之後,堆疊的內容如下:

不算遞迴呼叫語句本身,到目前為止所執行的語句只是除法運算以及對quotient的值進行測試。由於遞迴呼叫這些語句重複執行,所以它的效果類似迴圈:當quotient的值非零時,把它的值作為初始值重新開始迴圈。但是,遞迴呼叫將會儲存一些資訊(這點與迴圈不同),也就好是儲存在堆疊中的變數值。這些資訊很快就會變得非常重要。

現在quotient的值變成了零,遞迴函式便不再呼叫自身,而是開始列印輸出。然後函式返回,並開始銷毀堆疊上的變數值。

每次呼叫putchar得到變數value的最後乙個數字,方法是對value進行模10取餘運算,其結果是乙個0到9之間的整數。把它與字元常量『0』相加,其結果便是對應於這個數字的ascii字元,然後把這個字元列印出來。

輸出4:

接著函式返回,它的變數從堆疊中銷毀。接著,遞迴函式的前一次呼叫重新繼續執行,她所使用的是自己的變數,他們現在位於堆疊的頂部。因為它的value值是42,所以呼叫putchar後列印出來的數字是2。

輸出42:

接著遞迴函式的這次呼叫也返回,它的變數也被銷毀,此時位於堆疊頂部的是遞迴函式再前一次呼叫的變數。遞迴呼叫從這個位置繼續執行,這次列印的數字是6。在這次呼叫返回之前,堆疊的內容如下:

輸出426:

現在我們已經展開了整個遞迴過程,並回到該函式最初的呼叫。這次呼叫列印出數字7,也就是它的value引數除10的餘數。

輸出4267:

然後,這個遞迴函式就徹底返回到其他函式呼叫它的地點。

如果你把列印出來的字元乙個接乙個排在一起,出現在印表機或螢幕上,你將看到正確的值:4267

C 陣列 詳細分析

c 陣列 詳細分析 摘自 1 陣列下標 1 在定義時必須明確。只能用正整數或const常量,靜態或全域性變數不可以 例如 const int length 5 int array length 特 在new時可以用變數做下標。例如 int array new int variable 2 在初始化時...

C 陣列 詳細分析

1 陣列下標 1 在定義時必須明確。只能用正整數或const常量,靜態或全域性變數不可以 例如 const int length 5 int array length 特 在new時可以用變數做下標。例如 int array new int variable 2 在初始化時 一維陣列,可略 例如 i...

const詳細分析

最近在分析 linux 驅動的過程過程中遇到一些關於 const 的使用,現在在這裡詳細剖析一下 一,const int p 首先分析一下幾個概念 1 p 是乙個指標變數,因而它也是乙個變數,所謂變數就有變數的位址和變數的值,而這裡 p變數的值就是乙個位址,該位址下存放的是乙個整數,p的值等於這個整...