遞歸與迭代


遞歸和迭代的區別


遞歸就是不斷調用自身,就是A調用A的過程。遞歸會不斷使用棧來存過程,佔用的空間很大。很容易造成堆棧溢出的情況,改進可以使用尾遞歸的形式。遞歸有兩個過程:

1)遞推;

2)迴歸。

對於1+2+3+…+n這種問題,遞歸方式求解:

1
2
3
4
5
6
7
int sum1(int n)
{
    if(n == 0)
        return 0;
    else
        return n+sum1(n-1);
}
佔用的空間形式是:

sum(5)
5+sum(4)
5+4+sum(3)
5+4+3+sum(2)
5+4+3+2+sum(1)
5+4+3+2+1+sum(0)
5+4+3+2+1+0
5+4+3+2+1
5+4+3+3
5+4+6
5+10
15



迭代是A不斷的調用B的過程,使用一個循環語句的形式。

使用迭代的方式:

1
2
3
4
5
6
7
int sum2(int n)
{
    int ret = 0;
    for(int i = 1;  i <= n; i++)
              ret += i;
    return ret;
}

計算的過程是:

0+1=1
1+2=3
3+3=6
6+4=10
10+5=15

尾遞歸:

一個遞歸函數,它的計算過程是迭代的,就是尾遞歸函數,記住遞歸調用在遞歸實例中恰好以最後一步操作的形式出現。

Here's a tail-recursive version of the same function:

int tailrecsum(x, running_total=0):
    if x == 0:
        return running_total
    else:
        return tailrecsum(x - 1, running_total + x)

Here's the sequence of events that would occur if you called tailrecsum(5), (which would effectively be tailrecsum(5, 0), because of the default second argument).

tailrecsum(5, 0)
tailrecsum(4, 5)
tailrecsum(3, 9)
tailrecsum(2, 12)
tailrecsum(1, 14)
tailrecsum(0, 15)
15


這是尾遞歸:
function f(x) {
   if (x === 1) return 1;
   return f(x-1);
}

(這個程序沒什麼意義,僅作爲理解輔助之用)。

這不是尾遞歸:

function f(x) {
   if (x === 1) return 1;
   return 1 + f(x-1);
}

後者不是尾遞歸,是因爲該函數的最後一步操作是用1加上f(x-1)的返回結果,因此,最後一步操作不是調用自身。注意,後面這段代碼的遞歸也發生在函數末尾,但它不是尾遞歸。尾遞歸的判斷標準是函數運行最後一步是否調用自身,而不是是否在函數的最後一行調用自身。因此階乘n!的遞歸求法,儘管看起來遞歸發生在函數末尾,其實也不是尾遞歸:

function factorial(n) {
   if (n === 1) return 1;
   return n * factorial(n-1); // 最後一步不是調用自身,因此不是尾遞歸
}

使用尾遞歸可以帶來一個好處:因爲進入最後一步後不再需要參考外層函數(caller)的信息,因此沒必要保存外層函數的stack,遞歸需要用的stack只有目前這層函數的,因此避免了棧溢出風險。

一些語言提供了尾遞歸優化,當識別出使用了尾遞歸時,會相應地只保留當前層函數的stack,節省內存。

所以,在沒有尾遞歸優化的語言,如java, python中,鼓勵用迭代iteration來改寫尾遞歸;在有尾遞歸優化的語言如Erlang中,鼓勵用尾遞歸來改寫其他形式的遞歸。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章