遞迴思想和例項
先給一個簡單的階乘例子:
public static int getDg(int x){
System.out.println(x);
if (x==1) {
return 1;
}
if (x<4) {
return x * getDg(x-1);
} else {
return x * getDg(x-1);
}
}
很多人對遞迴的理解不太深刻。一直就停留在“自己呼叫自己”的程度上。這其實這只是遞迴的表象(嚴格來說連表象都概括得不全面,因為除了“自己呼叫自己”的遞迴外,還有互動呼叫的遞迴)。而遞迴的思想遠不止這麼簡單。
遞迴,並不是簡單的“自己呼叫自己”,也不是簡單的“互動呼叫”。它是一種分析和解決問題的方法和思想。簡單來說,遞迴的思想就是:把問題分解成為規模更小的、具有與原問題有著相同解法的問題。比如二分查詢演算法,就是不斷地把問題的規模變小(變成原問題的一半),而新問題與原問題有著相同的解法。
有些問題使用傳統的迭代演算法是很難求解甚至無解的,而使用遞迴卻可以很容易的解決。比如漢諾塔問題。但遞迴的使用也是有它的劣勢的,因為它要進行多層函式呼叫,所以會消耗很多堆疊空間和函式呼叫時間。
既然遞迴的思想是把問題分解成為規模更小且與原問題有著相同解法的問題,那麼是不是這樣的問題都能用遞迴來解決呢?答案是否定的。並不是所有問題都能用遞迴來解決。那麼什麼樣的問題可以用遞迴來解決呢?一般來講,能用遞迴來解決的問題必須滿足兩個條件:
- 可以通過遞迴呼叫來縮小問題規模,且新問題與原問題有著相同的形式。
- 存在一種簡單情境,可以使遞迴在簡單情境下退出。
如果一個問題不滿足以上兩個條件,那麼它就不能用遞迴來解決。
為了方便理解,還是拿斐波那契數列來說下:求斐波那契數列的第N項的值。
這是一個經典的問題,說到遞迴一定要提到這個問題。斐波那契數列這樣定義:f(0) = 0, f(1) = 1, 對n > 1, f(n) = f(n-1) + f(n-2)
這是一個明顯的可以用遞迴解決的問題。讓我們來看看它是如何滿足遞迴的兩個條件的:
- 對於一個n>2, 求f(n)只需求出f(n-1)和f(n-2),也就是說規模為n的問題,轉化成了規模更小的問題;
- 對於n=0和n=1,存在著簡單情境:f(0) = 0, f(1) = 1。
因此,我們可以很容易的寫出計算費波納契數列的第n項的遞迴程式:
int fib(n){
if(n == 0)
return 0;
else if(n == 1)
return 1;
else
return f(n-1) + f(n-2);
}
在編寫遞迴呼叫的函式的時候,一定要把對簡單情境的判斷寫在最前面,以保證函式呼叫在檢查到簡單情境的時候能夠及時地中止遞迴,否則,你的函式可能會永不停息的在那裡遞迴呼叫了。
延伸閱讀
此文章所在專題列表如下:
- 漫談遞迴:遞迴的思想
- 漫談遞迴:遞迴需要滿足的兩個條件
- 漫談遞迴:字串迴文現象的遞迴判斷
- 漫談遞迴:二分查詢演算法的遞迴實現
- 漫談遞迴:遞迴的效率問題
- 漫談遞迴:遞迴與迴圈
- 漫談遞迴:迴圈與迭代是一回事嗎?
- 遞迴計算過程與迭代計算過程
- 漫談遞迴:從斐波那契開始瞭解尾遞迴
- 漫談遞迴:尾遞迴與CPS
- 漫談遞迴:補充一些Continuation的知識
- 漫談遞迴:PHP裡的尾遞迴及其優化
- 漫談遞迴:從彙編看尾遞迴的優化
相關連結:http://www.nowamagic.net/librarys/veda/detail/2315