遞歸:跳臺階問題

本文轉自:http://zhedahht.blog.163.com/blog/static/25411174200731844235261/

http://zhedahht.blog.163.com/blog/static/25411174200722991933440/

題目:一個臺階總共有n級,如果一次可以跳1級,也可以跳2級。求總共有多少總跳法,並分析算法的時間複雜度。

分析:這道題最近經常出現,包括MicroStrategy等比較重視算法的公司都曾先後選用過個這道題作爲面試題或者筆試題。

首先我們考慮最簡單的情況。如果只有1級臺階,那顯然只有一種跳法。如果有2級臺階,那就有兩種跳的方法了:一種是分兩次跳,每次跳1級;另外一種就是一次跳2級。

現在我們再來討論一般情況。我們把n級臺階時的跳法看成是n的函數,記爲f(n)。當n>2時,第一次跳的時候就有兩種不同的選擇:一是第一次只跳1級,此時跳法數目等於後面剩下的n-1級臺階的跳法數目,即爲f(n-1);另外一種選擇是第一次跳2級,此時跳法數目等於後面剩下的n-2級臺階的跳法數目,即爲f(n-2)。因此n級臺階時的不同跳法的總數f(n)=f(n-1)+(n-2)

我們把上面的分析用一個公式總結如下:

        /  1                          n=1
f(n)=      2                          n=2
        \  f(n-1)+f(n-2)               n>2

分析到這裏,相信很多人都能看出這就是我們熟悉的Fibonacci序列。

 題目:定義Fibonacci數列如下:

        /  0                      n=0
f(n)=      1                      n=1
        \  f(n-1)+f(n-2)          n=2

輸入n,用最快的方法求該數列的第n項。

分析:在很多C語言教科書中講到遞歸函數的時候,都會用Fibonacci作爲例子。因此很多程序員對這道題的遞歸解法非常熟悉,看到題目就能寫出如下的遞歸求解的代碼。

long long Fibonacci_Solution1(unsigned int n)
{
      int result[2] = {0, 1};
      if(n < 2)
            return result[n];

      return Fibonacci_Solution1(n - 1) + Fibonacci_Solution1(n - 2);
}

但是,教科書上反覆用這個題目來講解遞歸函數,並不能說明遞歸解法最適合這道題目。我們以求解f(10)作爲例子來分析遞歸求解的過程。要求得f(10),需要求得f(9)f(8)。同樣,要求得f(9),要先求得f(8)f(7)……我們用樹形結構來表示這種依賴關係

                  f(10)
               /        \
            f(9)         f(8)
          /     \       /    \
       f(8)     f(7)  f(7)   f(6)
      /   \     /   \
 
   f(7)  f(6)  f(6) f(5)

我們不難發現在這棵樹中有很多結點會重複的,而且重複的結點數會隨着n的增大而急劇增加。這意味這計算量會隨着n的增大而急劇增大。事實上,用遞歸方法計算的時間複雜度是以n的指數的方式遞增的。大家可以求Fibonacci的第100項試試,感受一下這樣遞歸會慢到什麼程度。在我的機器上,連續運行了一個多小時也沒有出來結果。

其實改進的方法並不複雜。上述方法之所以慢是因爲重複的計算太多,只要避免重複計算就行了。比如我們可以把已經得到的數列中間項保存起來,如果下次需要計算的時候我們先查找一下,如果前面已經計算過了就不用再次計算了。

更簡單的辦法是從下往上計算,首先根據f(0)f(1)算出f(2),在根據f(1)f(2)算出f(3)……依此類推就可以算出第n項了。很容易理解,這種思路的時間複雜度是O(n)


long long Fibonacci_Solution2(unsigned n)
{
      int result[2] = {0, 1};
      if(n < 2)
            return result[n];

      long long  fibNMinusOne = 1;
      long long  fibNMinusTwo = 0;
      long long  fibN = 0;
      for(unsigned int i = 2; i <= n; ++ i)
      {
            fibN = fibNMinusOne + fibNMinusTwo;

            fibNMinusTwo = fibNMinusOne;
            fibNMinusOne = fibN;
      }

       return fibN;
}

最快的計算方法是找到公式,根據公式計算出第n項,時間複雜度爲O(1)。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章