動態規劃(1)使用斐波那契數列引入了動態規劃的概念

9-1 使用斐波那契數列引入了動態規劃的概念

1、計算斐波那契數列的第 \(n\) 項數值

一、斐波那契數列的定義

斐波那契數列是經過"遞歸"定義的,經過這個遞歸關係式,咱們能夠知道斐波那契數列中任意一個位置的數值。python

\[ \begin{equation}\begin{split} F(0) & = 0,\\ F(1) & = 1,\\ F(n) & = F(n-1) + F(n-2),\\ \end{split}\end{equation} \]數組

二、第 1 版 Python 代碼實現:使用斐波那契數列的定義式子遞歸實現

很容易地,咱們能寫出下面的代碼:緩存

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

說明:ide

  1. 代碼自己用於計算是沒有問題的,可是仔細研究,咱們就會發現,咱們雖然使用遞歸實現了斐波那契數列在任意位置的值的計算,可是,若是要咱們本身計算的話,確定不會這樣計算,由於太耗時了。優化

  2. 耗時的緣由在於,在上述的遞歸實現中,存在大量的重複計算,例如:
    要計算 fib(4),就得計算 fib(3) 和 fib(2),
    要計算 fib(3),就得計算 fib(2) 和 fib(1),
    此時 fib(2) 就被重複計算了,下面是一張圖,展現了部分重複計算的過程。idea

  1. 要解決上一步的問題,就要避免重複計算,咱們能夠引入一個 memo 數組,用於存入已經計算過一次的 fib 的值,下一次須要這個值的時候,再從中取,下面是代碼實現。

三、第 2 版 Python 代碼實現:加入了記憶化搜索,即便用了緩存數組,以免重複計算

memo = None


def _fib(n):
    if memo[n] != -1:
        return memo[n]
    if n == 0:
        return 0
    if n == 1:
        return 1
    memo[n] = _fib(n - 1) + _fib(n - 2)
    return memo[n]


def fib(n):
    global memo
    memo = [-1] * (n + 1)
    return _fib(n)

四、第 3 版 Python 代碼實現:雖然很簡單,可是咱們就能夠稱之爲「動態規劃」的解法

這個版本是最接近咱們本身去計算斐波那契數列的第 \(n\) 項。想想的確是這樣,聰明的你必定不會遞歸去計算波那契數列的,由於咱們的腦容量是有限,不太適合作太深的遞歸思考,雖然計算機在行遞歸,可是咱們也沒有必要讓計算機作重複的遞歸工做。spa

def fib(n):
    memo = [-1] * (n + 1)
    memo[0] = 0
    memo[1] = 1

    for i in range(2, n + 1):
        memo[i] = memo[i - 1] + memo[i - 2]
    return memo[n]

2、什麼是「記憶化搜索」

針對一個遞歸問題,若是它呈樹形結構,而且出現了不少」重疊子問題」,會致使計算效率低下,「記憶化搜索」就是針對」重疊子問題」的一個解決方案,實際上就是」加緩存避免重複計算」。code

3、什麼是「動態規劃」

(1)比較「記憶化搜索」與「動態規劃」

由上面的介紹咱們就能夠引出動態規劃的概念:遞歸

  • "記憶化搜索"或者咱們稱"重疊子問題"的加緩存優化的實現,咱們的思考路徑是"自頂向下"。即爲了解決數據規模大的問題,咱們「假設」已經解決了數據規模較小的子問題。
  • 「動態規劃」就是上述"循環版本"的實現,咱們思考問題路徑是"自下而上"。實際上,咱們是先「真正地」解決了數據規模較小的問題,而後一步一步地解決了數據規模較大的問題。

(2)「動態規劃」的官方定義

下面咱們給出「動態規劃」的官方定義:it

dynamic programming (also known as dynamic optimization) is a method for solving a complex problem by breaking it down into a collection of simpler subproblems, solving each of those subproblems just once, and storing their solutions – ideally, using a memory-based data structure.

將原問題拆解成若干子問題,同時保存子問題的答案,使得每一個子問題只求解一次,最終得到原問題的答案。

(3)針對「動態規劃」問題的通常思考路徑

咱們一般的作法是:使用記憶化搜索的思路思考原問題,可是使用動態規劃的方法來實現。即「從上到下」思考,可是「從下到上」實現。

4、總結

對於一個遞歸結構的問題,若是咱們在分析它的過程當中,發現了它有不少「重疊子問題」,雖然並不影響結果的正確性,可是咱們認爲大量的重複計算是不環保,不簡潔,不優雅,不高效的,所以,咱們必須將「重疊子問題」進行優化,優化的方法就是「加入緩存」,「加入緩存」的一個學術上的叫法就是「記憶化搜索」。

另外,咱們還發現,直接分析遞歸結構,是假設更小的子問題已經解決給出的實現,思考的路徑是「自頂向下」。但有的時候,「自底向上」的思考路徑每每更直接,這就是「動態規劃」,咱們是真正地解決了更小規模的問題,在處理更大規模的問題的時候,直接使用了更小規模問題的結果。

相關文章
相關標籤/搜索