1. 程式人生 > >用遞迴,迭代,通項公式三種方法實現斐波那契數列求解

用遞迴,迭代,通項公式三種方法實現斐波那契數列求解

斐波那契數列指的是這樣一個數列:1、1、2、3、5、8、13、21、……

   這個數列從第三項開始,每一項都等於前兩項之和。它的通項公式為:(1/√5)*{[(1+√5)/2]^n -[(1-√5)/2]^n}(又叫“比內公式”,是用無理數表示有理數的一個範例。)(√5表示根號5)
    有趣的是:這樣一個完全是自然數的數列,通項公式居然是用無理數來表達的。

    用數學公式表示出來就是:
          F(1)=1,F(2)=1     (n=1,2)
         F(n)=F(n-1)+F(n-2)     (n>2)

     有三種比較常用的求解第n項斐波那契數列的方法:遞迴法、迭代法、通項公式法。


1、遞迴法

概述  遞迴做為一種演算法在程式設計語言中廣泛應用.是指函式/過程/子程式在執行過程中直接或間接呼叫自身而產生的重入現象.遞迴是電腦科學的一個重要概念,遞迴的方法是程式設計中有效的方法,採用遞迴編寫程式能使程式變得簡潔和清晰.。

 定義

一般定義


  程式呼叫自身的程式設計技巧稱為遞迴( recursion)。

  一個過程或函式在其定義或說明中又直接或間接呼叫自身的一種方法,它通常把一個大型複雜的問題層層轉化為一個與原問題相似的規模較小的問題來求解,遞迴策略只需少量的程式就可描述出解題過程所需要的多次重複計算,大大地減少了程式的程式碼量。遞迴的能力在於用有限的語句來定義物件的無限集合。一般來說,遞迴需要有邊界條件、遞迴前進段和遞迴返回段。當邊界條件不滿足時,遞迴前進;當邊界條件滿足時,遞迴返回。

  注意:

  (1) 遞迴就是在過程或函式裡呼叫自身;

  (2) 在使用遞迴策略時,必須有一個明確的遞迴結束條件,稱為遞迴出口。

其它定義


  遞迴的另一種定義:

  遞迴,就是用自己的簡單情況,定義自己。

  在數學和電腦科學中,遞迴指由一種(或多種)簡單的基本情況定義的一類物件或方法,並規定其他所有情況都能被還原為其基本情況。

  例如,下列為某人祖先的遞迴定義:

  某人的雙親是他的祖先(基本情況)。 某人祖先的雙親同樣是某人的祖先(遞迴步驟)。斐波那契數列是典型的遞迴案例:

  Fib(0) = 0 [基本情況] Fib(1) = 1 [基本情況] 對所有n >1的整數:Fib(n) = (Fib(n-1) + Fib(n-2)) [遞迴定義]儘管有許多數學函式均可以遞迴表示,但在實際應用中,遞迴定義的高開銷往往會讓人望而卻步。例如:

  階乘(1) = 1 [基本情況] 對所有n > 1的整數:階乘(n) = (n *階乘(n-1)) [遞迴定義]一種便於理解的心理模型,是認為遞迴定義對物件的定義是按照“先前定義的”同類物件來定義的。例如:你怎樣才能移動100個箱子?答案:你首先移動一個箱子,並記下它移動到的位置,然後再去解決較小的問題:你怎樣才能移動99個箱子?最終,你的問題將變為怎樣移動一個箱子,而這是你已經知道該怎麼做的。

  如此的定義在數學中十分常見。例如,集合論對自然數的正式定義是:1是一個自然數,每個自然數都有一個後繼,這一個後繼也是自然數。

遞迴應用  遞迴演算法一般用於解決三類問題:

  (1)資料的定義是按遞迴定義的。(Fibonacci函式)

  (2)問題解法按遞迴演算法實現。(回溯)

  (3)資料的結構形式是按遞迴定義的。(樹的遍歷,圖的搜尋)

  遞迴的缺點:

  遞迴演算法解題的執行效率較低。在遞迴呼叫的過程當中系統為每一層的返回點、區域性量等開闢了棧來儲存。遞迴次數過多容易造成棧溢位等。

這種方法的優點是簡潔和容易理解,缺點是時間複雜度太大,隨著n的增大,運算時間將會急劇增加。因此在很多場合這種方法是不可取的。
使用這種方法的關鍵程式碼是:
if(n == 1|| n== 2)
{
    return1;
}
else
{
    return fib(n- 1) + fib(n - 2);
}

#include<stdio.h>
int main()
{
    longint  fib[41] = {0,1};
    inti;

   for(i=2;i<41;i++)
  fib[i] =fib[i-1]+fib[i-2];


   for(i=1;i<41;i++)
  printf("F%d=%d\n",i,fib[i]);

   
    return0;
}


2、迭代法
這種方法相對於遞迴法來說在時間複雜度上減小了不少,但程式碼相對就要複雜些了。

#include<stdio.h>
int f(int n);
int main()

{
 int n;
   scanf("%d",&n);
 f(n);
}

int f(int n)
{
 int i,f1=1,f2=1,f3;

 if(n<=0)
 {
  printf("輸入錯誤.\n");
    
 }
 else if(n==1||n==2)
 {
  printf("1");
 }
 else
 {
  for(i=0;i<n-2;i++)
  {
           f3=f1+f2;          //f1表示當前的值
   f2=f1;
   f1=f3;
  }
  
  printf("%d\n",f1);
 }
 
}

利用迭代演算法解決問題,需要做好以下三個方面的工作:

   一、確定迭代變數。在可以用迭代演算法解決的問題中,至少存在一個直接或間接地不斷由舊值遞推出新值的變數,這個變數就是迭代變數。

   二、建立迭代關係式。所謂迭代關係式,指如何從變數的前一個值推出其下一個值的公式(或關係)。迭代關係式的建立是解決迭代問題的關鍵,通常可以使用遞推或倒推的方法來完成。

   三、對迭代過程進行控制。在什麼時候結束迭代過程?這是編寫迭代程式必須考慮的問題。不能讓迭代過程無休止地重複執行下去。迭代過程的控制通常可分為兩種情況:一種是所需的迭代次數是個確定的值,可以計算出來;另一種是所需的迭代次數無法確定。對於前一種情況,可以構建一個固定次數的迴圈來實現對迭代過程的控制;對於後一種情況,需要進一步分析出用來結束迭代過程的條件。


3、通項公式法
這種方法是最沒技術含量的方法,只要你知道通項公式照著把它翻譯成程式語言就可以了,優點不言而喻。
fib(n) = pow(((1 + sqrt(5)) / 2.0),n) / sqrt(5) - pow(((1 -sqrt(5)) / 2.0),n) / sqrt(5));

總結:
    這三種方法各有優缺點,使用哪種方法根據實際情況確定。

 

   從時間複雜度上來說O(通向公式法)<O(迭代法)<O(遞迴法)。