http://www.nowamagic.net/librarys/veda/detail/2300
在高級語言中,調用自己和其他函數並沒有本質的不同。我們把一個直接調用自己或通過一系列的調用語句間接地調用自己的函數,稱做遞歸函數。
-
當然,寫遞歸程序最怕的就是陷入永不結束的無窮遞歸中,所以,毎個遞歸定義必須至少有一個條件,滿足時遞歸不再進行,即不再引用自身而是返回值退出。比如剛才的例子,總有一次遞歸會使得i < 2的,這樣就可以執行return i的語句而不用繼續遞歸了。
對比了兩種實現斐波那契的代碼。迭代和遞歸的區別是:迭代使用的是循環結構,遞歸使用的是選擇結構。
- 遞歸能使程序的結構更清晰、更簡潔、更容易讓人理解,從而減少讀懂代碼的時間。但是大量的遞歸調用會建立函數的副本,會耗費大量的時間和內存。
- 迭代則不需要反復調用函數和占用額外的內存。因此我們應該視不同 情況選擇不同的代碼實現方式。
那么我們講了這么多遞歸的內容,和棧有什么關系呢?這得從計算機系統的內部說起。
-
前面我們已經看到遞歸是如何執行它的前行和退回階段的。遞歸過程退回的順序是它前行順序的逆序。在退回過程中,可能要執行某些動作,包括恢復在前行過程中存儲起來的某些數據。
這種存儲某些數據,並在后面又以存儲的逆序恢復這些數據,以提供之后使用的需求,顯然很符合棧這樣的數據結構,因此,編譯器使用棧實現遞歸就沒什么好驚訝的了。
簡單的說,就是在前行階段,對於每一層遞歸,函數的局部變量、參數值以及返回地址都被壓入棧中。在退回階段,位於棧頂的局部變量、參數值和返回地址被彈出,用於返回調用層次中執行代碼的其余部分,也就是恢復了調用的狀態。
-
遞歸調用其實就是棧,棧有先進后出的特點,遞歸調用的實質也就是循環調用,我寫一個簡單的例子吧:
01 |
#include "stdio.h" |
02 |
03 |
int main() |
04 |
{ |
05 |
int n; |
06 |
int f( int m); |
07 |
printf ( "請輸入一個大於1的數:" ); |
08 |
scanf ( "%d" ,&n); |
09 |
printf ( "%d\n" ,f(n)); |
10 |
return 0; |
11 |
} |
12 |
13 |
int f( int m) |
14 |
{ |
15 |
if (m==1) |
16 |
return 1; |
17 |
else |
18 |
{ |
19 |
printf ( "m=%d\n" ,m); |
20 |
//每當m!=1的時候就不斷地調用int f(int m),直到m==1為止 |
21 |
return f(m-1); |
22 |
} |
23 |
} |
程序執行結果:
1 |
請輸入一個大於1的數:5 |
2 |
m=5 |
3 |
m=4 |
4 |
m=3 |
5 |
m=2 |
6 |
1 |
-
這個程序是這樣執行的,首先你先隨便輸入一個整數n,對於每個整數把n的值傳給m,這樣就實現了函數的第一次調用。然后調用了f(n),緊接着執行int f(int m),當m不等於1的時候就執行了else語句,return f(m-1),這樣f(m-1)又一次調用了int f(int m),直到m=1為止。最終返回了f(n)的值,遞歸調用也到此為止。
下面附上動畫演示:
當然,對於現在的髙級語言,這樣的遞歸問題是不需要用戶來管理這個棧的,一切都由系統代勞了。
延伸閱讀
此文章所在專題列表如下:
- 棧的定義與大概理解
- 棧的抽象數據類型ADT
- 順序棧:棧的順序存儲結構
- 順序棧的進棧操作
- 順序棧的出棧操作
- 獲取順序棧的棧頂元素
- 鏈棧:棧的鏈式存儲結構
- 鏈棧的進棧操作
- 鏈棧的初始化與遍歷
- 鏈棧的出棧操作
- 鏈棧的置空操作與判斷鏈棧是否為空
- 為什么要使用棧這種數據結構
- 遞歸,棧的重要應用之一
- 棧是如何實現遞歸的
- 接觸后綴表達式(逆波蘭表示法)
- 圖解后綴表達式的計算過程
- 將中綴表達式轉化為后綴表達式
- 開始學習隊列這個數據結構
- 隊列的抽象數據類型ADT
- 順序隊列:隊列的順序存儲結構
- 順序隊列的入隊操作
- 順序隊列的出隊操作
- 順序隊列置空與判斷操作
- 隊列順序存儲結構的不足
- 關於循環隊列的一些講解
- 鏈隊列:隊列的鏈式存儲結構
- 鏈隊列的初始化操作
- 鏈隊列的入隊操作
- 鏈隊列的出隊操作
- 補完鏈隊列的其它常見操作
- 循環隊列與鏈隊列的優劣勢