遞歸和尾遞歸的比較,斐波那契

相信若是一我的讓咱們求一個斐波那契數列,若是你學過c語言,你必定會說用遞歸法啊,很容易就實現了,可是若是人家讓你求斐波那契的第50個數,並且你對遞歸瞭解的話,估計幫你不會說遞歸了,若是瞭解夠深的話,其實你會說遞歸也能夠求出來。
一、遞歸
       首先咱們來講說什麼是遞歸,簡單的來講,就是一個函數須要調用本身來完成某種功能,這種調用就叫作遞歸。
       但咱們須要清楚一點,遞歸在使用的時候,並非一直調用本身,咱們須要給他一個停下來的時機。就像打仗同樣,要知道進攻的路線,但若是遇到突發情況也要能及時撤退。因此咱們的遞歸也同樣,你須要給他一條前進路徑也要給他一條返回路徑。因此 在使用遞歸策略時,必須有一個明確的遞歸結束條件,稱爲遞歸出口。
      但咱們都清楚,遞歸有它致命的缺點,在遞歸調用的過程中系統爲每一層的返回點、局部量等開闢了棧來存儲,所以遞歸次數過多容易形成棧溢出,正是由於如此它和循環比較效率是很低的,這也就是我前面所說的若是讓你計算斐波那契的第五十個數你直接用遞歸法去求實際上是不太好的。
 
下面咱們來看看程序:
int Fib(int n)
{
    if( n < 2)
         return n;
     return (Fib(n-1)+Fib(n-2));
}
這樣寫出來的代碼很簡潔,來分析一下它的執行過程,咱們給n=5:
可能這樣你還看不出問題,其實上面的圖至關是一個樹狀結構:
     
   紅色的部分在以後又會被求到,若是咱們給的數值不是5是一個更大的數,則被重複計算和調用的數和次數會變得更多。可見,在這樣一個過程當中,咱們把某些值一直在重複計算,再加上重複的開闢棧空間,使得它的效率變得很是低,大家能夠試着求一下第40 50個斐波那契額。
 
 
二、尾遞歸
   尾部遞歸是一種編程技巧。若是在遞歸函數中,遞歸調用返回的結果總被直接返回,則稱爲尾部遞歸。尾部遞歸的函數有助將算法轉化成函數編程語言,並且從編譯器角度來講,亦容易優化成爲普通循環。這是由於從電腦的基本面來講,全部的循環都是利用重複移跳到代碼的開頭來實現的。若是有尾部歸遞,就只須要疊套一個堆棧,由於電腦只須要將函數的參數改變再從新調用一次。
複製代碼
 1 int Fib(int n, int ret1, int ret2)
 2  
 3 {
 4     if (n ==0 )
 5     {
 6         return ret1;
 7     }
 8     else
 9     {
10  return Fib(n - 1, ret2, ret1 +ret2);
11     }
12 }
複製代碼

 

 
它的執行步驟以下,每次的ret1就是要求當前的返回值,當執行到n減到0的時候,此時的ret1就是咱們要求的第n個數:
 
 
這裏咱們在傳參的時候須要傳ret=0,ret2=1; Fib(n - 1, ret2, ret1 +ret2)的使用,本來樸素的遞歸產生的棧的層次像二叉樹同樣,以指數級增加,可是如今棧的層次卻像是數組,變成線性增加了,實在是奇妙,總結起來也很簡單,本來棧是先擴展開,而後邊收攏邊計算結果,如今卻變成在調用自身的同時經過參數來計算。
ret1就是第n個數,而ret2就是第n與第n+1個數的和,這其實和咱們的「迭代」異曲同工。
咱們能夠看看迭代寫出來的斐波那契數列求法。
三、迭代法
int Fib(int n)
{
    int num1 = 1;
    int num2 = num1;
    int num3 = num1;
    while (n > 2)
    {
        num3 = num1 + num2;
        num1 = num2;
        num2 = num3;
        n--;
    }
    return num3;
}
能夠看出迭代法實現的方法其實和咱們的尾遞歸法一個道理,可是迭代法比較通俗易懂,並且和尾遞歸比較起來,由於不用開闢棧空間,因此這三種方法比較起來迭代法是效率最高的。咱們在解決實際問題的時候,根據實際要求選擇合適的方法。
相關文章
相關標籤/搜索