1.什么是遞歸函數(recursive function)
遞歸函數即自調用函數,在函數體內部直接或間接地自己調用自己,即函數的嵌套調用是函數本身。
例如,下面的程序為求n!:
long fact(int n) { if(n==1) return 1; return fact(n-1)*n; //出現函數自調用 }
2.函數調用機制的說明
任何函數之間不能嵌套定義, 調用函數與被調用函數之間相互獨立(彼此可以調用)。 發生函數調用時,被調函數中保護了調用函數的運行環境和返回地址,使得調用函數的狀態可以在被調函數運行返回后完全恢復,而且該狀態與被調函數無關。
被調函數運行的代碼雖是同一個函數的代碼體,但由於調用點,調用時狀態, 返回點的不同,可以看作是函數的一個副本,與調用函數的代碼無關,所以函數的代碼是獨立的。被調函數運行的棧空間獨立於調用函數的棧空間,所以與調用函數之間的數據也是無關的。函數之間靠參數傳遞和返回值來聯系,函數看作為黑盒。
這種機制決定了C/C++允許函數遞歸調用。
3.遞歸調用的形式
遞歸調用有直接遞歸調用和間接遞歸調用兩種形式。
直接遞歸即在函數中出現調用函數本身。
例如,下面的代碼求斐波那契數列第n項。 斐波那契數列的第一和第二項是1,后面每一項是前二項之和,即1,1,2,3,5,8,13,...。 代碼中采用直接遞歸調用:
long fib(int x) { if(x>2) return(fib(x-1)+fib(x-2)); //直接遞歸 else return 1; }
間接遞歸調用是指函數中調用了其他函數,而該其他函數卻又調用了本函數。例如,下面的代碼定義兩個函數,它們構成了間接遞歸調用:
int fnl(int a) { int b; b=fn2(a+1); //間接遞歸 //... } int fn2(int s) { int c; c=fnl(s-1); //間接遞歸 //... }
上例中,fn1()函數調用了fn2()函數,而fn2()函數又調用了fn1()函數。
4.遞歸的條件
(1)須有完成函數任務的語句。
例如,下面的代碼定義了一個遞歸函數:
#include void count(int val) //遞歸函數可以沒有返回值 { if(val>1) count(val-1); 、 cout<<"ok:" <<<="" 此語句完成函數任務="" />
該函數的任務是在輸出設備上顯示"ok:整數值”。
(2)—個確定是否能避免遞歸調用的測試
例如,上例的代碼中,語句"if(val>1)"便是—個測試, 如果不滿足條件,就不進行遞歸調用。
(3)一個遞歸調用語句。
該遞歸調用語句的參數應該逐漸逼近不滿足條件,以至最后斷絕遞歸。
例如,上面的代碼中,語句“if(val>1)” 便是一個遞歸調用,參數在漸漸變小,這種發展趨勢能使測試"if(val>1)”最終不滿足。
(4)先測試,后遞歸調用。
在遞歸函數定義中,必須先測試,后遞歸調用。也就是說,遞歸調用是有條件的,滿足了條件后,才可以遞歸。
例如,下面的代碼無條件調用函數自己,造成無限制遞歸,終將使棧空間溢出:
#include void count(int val) { count(val-1); //無限制遞歸 if(val>1) //該語句無法到達 cout <<"ok: " << }
5.消去遞歸
大多數遞歸函數都能用非遞歸函數來代替。例如,下面的代碼求兩個整數a,b的最大公約數,用遞歸和非遞歸函數分別定義之:
long gcdt(int a,int b) //遞歸版 { if(a%b==0) return b; return gcdl(b,a%b); } long gcd2(int a,int b) //非遞歸版 { int temp; while(b!=0) { temp=a%b; a=b; b=temp; } return a; }
思考:將求n!的遞歸函數非遞歸化。
6.遞歸的評價
遞歸的目的是簡化程序設計,使程序易讀。
但遞歸增加了系統開銷。 時間上, 執行調用與返回的額外工作要占用CPU時間。空間上,隨着每遞歸一次,棧內存就多占用一截。
相應的非遞歸函數雖然效率高,但卻比較難編程,而且相對來說可讀性差。
現代程序設計的目標主要是可讀性好。隨着計算機硬件性能的不斷提高,程序在更多的場合優先考慮可讀而不是高效,所以,鼓勵用遞歸函數實現程序思想。
(轉自 http://www.cnblogs.com/seaven/archive/2010/12/17/1908953.html)