遞迴與尾遞迴的迴圈實現
遞迴的思想是把問題分解成為規模更小且與原問題有著相同解法的問題,那麼是不是這樣的問題都能用遞迴來解決呢?答案是否定的。並不是所有問題都能用遞迴來解決。那麼什麼樣的問題可以用遞迴來解決呢?一般來講,能用遞迴來解決的問題必須滿足兩個條件:
1.可以通過遞迴呼叫來縮小問題規模,且新問題與原問題有著相同的形式;2.存在一種簡單情境,可以使遞迴在簡單情境下退出。
如果一個問題不滿足以上兩個條件,那麼它就不能用遞迴來解決。
遞迴分為兩個階段:
1)遞推:把複雜的問題的求解推到比原問題簡單一些的問題的求解;
2)迴歸:當獲得最簡單的情況後,逐步返回,依次得到複雜的解。
在編寫遞迴呼叫的方法的時候,最好把對簡單情境的判斷寫在最前面,以保證函式呼叫在檢查到簡單情境的時候能夠及時地中止遞迴,否則,你的函式可能會永不停息的在那裡遞迴呼叫了。
由於遞迴引起一系列的函式呼叫,並且有可能會有一系列的重複計算(如遞迴計算斐波那契數列),遞迴演算法的執行效率相對較低,浪費空間,並且遞迴太深容易造成堆疊的溢位。所以有時可以將某些遞迴轉換成為非遞迴的形式。遞迴轉換為非遞迴的情況有兩種:
1.尾遞迴可以轉變為迭代迴圈處理;
2.可以自己建立一個堆疊儲存這些區域性變數,替換系統棧。
尾遞迴。如果在遞迴函式中,遞迴呼叫返回的結果總被直接返回,則稱為尾遞迴。某些一般遞迴可以改寫為尾遞迴(如文後例子中斐波那契數列的尾遞迴形式),尾遞迴是對一般遞迴的優化,尾遞迴和一般遞迴的差別體現在引數上,本質是將上一次計算的結果記錄起來,通過引數傳遞給下次呼叫。一般遞迴如果呼叫層次過多,棧就容易滿造成溢位,尾遞迴可以消除函式呼叫棧過長的問題,但是必須藉助編譯器,每一個函式在呼叫下一個函式之前,都能做到先把當前自己佔用的棧給先釋放了,尾遞迴的呼叫鏈上可以做到只有一個函式在使用棧,此時棧可以一直使用。利用尾遞迴最主要的目的是優化,比一般遞迴節省了空間和時間。尾遞迴還
迭代 是用計算機解決問題的一種基本方法,可以算是迴圈的一種。它利用計算機運算速度快、適合做重複性操作的特點,讓計算機對一組指令(或一定步驟)進行重複執行,在每次執行這組指令(或這些步驟)時,都從變數的原值推出它的一個新值。所以迭代演算法有三方面:(1)確定迭代變數;(2)建立迭代關係式。所謂迭代關係式,指如何從變數的前一個值推出其下一個值的公式(或關係)。迭代關係式的建立是解決迭代問題的關鍵,通常可以使用遞推或倒推的方法來完成;(3)對迭代過程進行控制。在什麼時候結束迭代過程,這是編寫迭代程式必須考慮的問題,不能讓迭代過程無休止地重複執行下去。
尾遞迴可以用迴圈代替,轉變後能克服遞迴的一些不足。例如:計算階乘的遞迴計算過程屬於線性遞迴,步驟數目的增長正比於輸入n。也就是說,這個過程所需步驟的增長為O(n) ,空間需求的增長也為O(n) 。對於迭代的階乘,步數還是O(n)而空間是O(1) ,也就是常數。
不需要消解的遞迴
那種盲目的消解遞迴,不惜一切代價躲避遞迴,認為“遞迴的速度慢,為了提高速度,必須用棧或者其他的方法來消解的說法是很片面的。如果一個遞迴過程用非遞迴的方法實現後,速度提高了,那只是因為遞迴做了一些無用功。假使一個遞迴過程必須要用棧才能消解,那麼完全模擬後的結果根本就不會對速度有任何提升,只會減慢;如果你改完後速度提升了,那隻證明你的遞迴函式寫的有問題,如多了許多重複操作——開啟關閉檔案、連線斷開資料庫,而這些完全可以放到遞迴外面。如果把遞迴都翻譯成迭代或藉助棧的形式,就往往會模糊了程式的本質,以致使它非常難以理解。它們本質上是遞迴的,沒有簡單的迭代形式。這樣的遞迴演算法不宜轉化為非遞迴演算法。
例子:
public class TailRecursion {
//階乘的一般遞迴
static int factorial(int n)
{
if (n <= 1) return 1;
return (n * factorial(n-1));
}
//階乘的尾遞迴形式
static int factorial_tail(int n, int res)
{
if (n <= 1) return res;
return factorial_tail(n - 1, n * res);
}
//階乘的迭代形式
static int factorial_loop(int n)
{
int r = 1;
for(int i=1;i<=n;i++){
r=r*i;
}
return r;
}
//斐波那契數列的一般遞迴
static int fibonacci(int n)
{
if(n<=3)
return 1;
else{
return fibonacci(n-1)+fibonacci(n-2);
}
}
//斐波那契數列的尾遞迴形式
static int fibonacci_tail(int n,int acc1,int acc2)
{
if (n < 2)
{
return acc1;
}
else
{
return fibonacci_tail(n-1,acc2,acc1+acc2);
}
}
//斐波那契數列的迴圈方式
static int fibonacci_loop(int n)
{
int a=0;
int b=1;
if(n==1)
return 1;
if(n==2)
return 1;
for(int i=3;i<=n;i++){
int temp=a+b;
a=b;
b=temp;
}
return b;
}
public static void main(String[] args) {
int j=factorial_loop(5);
System.out.println(j);
int r=fibonacci_tail(8,0,1);
System.out.println(r);
}
}