A coding kata -- Fibonacci sequence

斐波拉契數列(Fibonacci sequence),想必讀者都已經很是熟悉了,指的是一個這樣的數列:
0112358132134……
這個數列從第3項開始,每一項都等於前兩項之和。python

伊始

如何用計算機解決這個問題,最簡單的辦法好像就是遞歸實現了,不知道讀者是否考慮過下面幾個小問題?算法

  • 遞歸的方法能夠求出全部的解嗎,當n=80800甚至8000或者更大值的時候能夠求解嗎
  • 效率怎麼樣
  • 你有什麼方法提升效率嗎
  • 你是否利用了某些編程語言的特性
  • 你能想出幾種不一樣的解法
  • 等等

本文主要用python來帶你們看看不一樣的解決方案,在某些方案中我也貼出用C++的解決方案,第一個就從遞歸的方法來看吧編程

遞歸實現

def fib(n):
    if n == 0: return 0
    elif n == 1: return 1
    else: return fib(n-1) + fib(n-2)

求解上面的遞歸式:
$$T(n) = T(n - 1) + T(n - 2) + 1 ≈ 2^n = O(2^n)$$
上面的實現十分低效,遞歸的過程當中執行了太多重複的動做,時間複雜度是指數級別,並且受遞歸深度的限制,求解的範圍十分有限。緩存

遞歸優化

def fib(n):
    def fib_iter(n, x, y):
        if n == 0:
            return x
        else:
            return fib_iter(n - 1, y, x + y)
    return fib_iter(n, 0, 1)

上面是尾遞歸優化的寫法,雖然減小了不少沒必要要的計算,但仍是受到遞歸深度的限制,下面咱們看幾個更好的解決方案app

動態規劃

  • 自底向上
def fib(n):
    memo = [0, 1]
    for i in range(2, n+1):
        memo.append(memo[i - 1] + memo[i - 2])
    return memo[n]
  • 自頂向下
memo = {0: 0, 1: 1}
def fib(n):
    if n in memo:
        return memo[n]

    if n == 0: return 0
    elif n == 1: return 1
    memo[n]  = fib(n - 1) + fib(n - 2)
    return memo[n]

上面兩種解決方案都將問題的複雜度降到了O(n)級別,缺陷是空間複雜度也是O(n)編程語言

  • 改進方案,高效緩存

經過遞歸式能夠知道,當前值只和前兩次的值相關,因此咱們只須要保留最後兩個數值便可優化

def fib(n):
    a, b = 0, 1
    for i in range(n):
        a, b = b, a + b
    return a

時間複雜度爲O(n),空間複雜度降到了O(1),一樣的咱們給出C++版本的實現spa

namespace {
struct fib_cache {
  fib_cache() : previous_{0}, last_{1}, size_{2} {}

  size_t size() const { return size_; }

  unsigned int operator[](unsigned int n) const {
    return n == size_ - 1 ? last_ :
           n == size_ - 2 ? previous_ :
           throw std::out_of_range("The value is no longer in the cache");
  }

  void push_back(unsigned int value) {
    size_++;
    previous_ = last_;
    last_ = value;
  }

private:
  unsigned int previous_;
  unsigned int last_;
  size_t size_;
};

} // namespace

unsigned int fib(unsigned int n) {
  fib_cache cache;
  if (cache.size() > n) {
    return cache[n];
  } else {
    const auto result = fib(n - 1) + fib(n - 2);
    cache.push_back(result);
    return result;
  }
}

上面的程序受到無符號整型最大值的約束,計算的範圍有限,讀者可使用C+11引入的大整形long long來解決,也能夠本身實現大整數的存儲計算。
上面的算法最快是O(n)級別的,下面咱們看一種O(logn)級別的算法code

矩陣算法

下面咱們先看一個fibnacci數列的矩陣關係推導遞歸

$$ \left[ \begin{matrix} F_n\\ F_{n-1}\\ \end{matrix} \right] = \left[ \begin{matrix} F_{n-1} + F_{n-2}\\ F_{n-1}\\ \end{matrix} \right] =\left[ \begin{matrix} 1 & 1 \\ 1 & 0\\ \end{matrix} \right] * \left[ \begin{matrix} F_{n-1} \\ F_{n-2}\\ \end{matrix} \right] $$

由上面的推導式,咱們能夠輕易獲得下面的結果

$$ \left[ \begin{matrix} F_n\\ F_{n-1}\\ \end{matrix} \right] = \left[ \begin{matrix} 1 & 1 \\ 1 & 0\\ \end{matrix} \right]^{n-1} * \left[ \begin{matrix} F_{1} \\ F_{0}\\ \end{matrix} \right] = \left[ \begin{matrix} 1 & 1\\ 1 & 0\\ \end{matrix} \right]^{n-1} * \left[ \begin{matrix} 1\\ 0\\ \end{matrix} \right] $$

上面的公式變成了求矩陣的冪,而矩陣的冪能夠用二分算法快速求冪(本質上屬於分治法的思想)

$$ x^n= \begin{cases} x^{n/2} * x^{n/2}, &even\\ x^{(n-1)/2} * x^{(n-1)/2} * x, &odd \end{cases} $$

求解上面的遞歸式:
$$T(n) = T(n/2) + 1 = O(log n)$$
這裏就不給出具體實現了,讀者能夠自行嘗試,也可使用python的第三方numpy庫去計算矩陣冪。

End

其實Fibonacci問題的解法還有不少高效的解法,等待着你的探索此時的終點也只是下個旅途的起點……

相關文章
相關標籤/搜索