動態規劃法(一)從斐波那契數列談起

liuxuhui發表於2021-09-09

動態規劃法與分治方法

  動態規劃(Dynamic Programming)與分治方法相似,都是透過組合子問題的解來求解原問題。不同的是,分治方法通常將問題劃分為互不相交子問題遞迴地求解子問題,再講它們的解組合起來,求出原問題的解。而動態規劃應用於子問題重疊的情況,即不用的子問題具有公共的子子問題。在這種情況下,如果採用分治演算法,則分治演算法會做許多不必要的工作,它會反覆地求解那些公共子子問題。對於動態規劃法,它對每個子子問題只求解一次,將其儲存在一個表格中,從而無需每次求解一個子子問題時都重新計算,避免了這種不必要的計算工作。
  也就是說,動態規劃法與分治方法相比,是用空間來換時間,而時間上獲得的效益是很客觀的,這是一種典型的時空平衡(time-memory trade-off)的策略。通常,動態規劃法用來求解最最佳化問題(optimization problem),如斐波那契數列求值問題,鋼條切割問題,0-1揹包問題,矩陣鏈乘法問題,最長公共子序列(LCS)問題,最優二叉搜尋樹問題等。
  一般情況下,動態規劃演算法的步驟如下:

  1. 刻畫一個最優解的結構特徵。

  2. 遞迴地定義最優解的值。

  3. 計算最優解的值,通常採用自底向上的方法。

  4. 利用計算出的資訊構造一個最優解。

  接下來,我們將從斐波那契數列求值這個簡單的例子入手,來分析動態規劃法的具體步驟和優點。

斐波那契數列

  斐波那契數列記為{f(n)}{f(n)},其表示式如下:

f(0)=0f(1)=1f(n)=f(n−1)+f(n−2),n≥2{f(0)=0f(1)=1f(n)=f(n1)+f(n2),n2


  具體寫出前幾項,就是:0,1,1,2,3,5,8,13,21,34,55,89,144,233......
  接下來,我們將會採用遞迴法和動態規劃法來求解該數列的第n項,即f(n)的值。


遞迴法求解

  首先,我們採用遞迴法來求解斐波那契數列的第n項f(n)f(n),其演算法描述如下:

function fib(n)
    if n = 0 return 0
    if n = 1 return 1
    return fib(n − 1) + fib(n − 2)

分析上述虛擬碼,先是定義一個函式fib(n),用來計算斐波那契數列的第n項,當n≥2n2時,它的返回值會呼叫函式fib(n-1)和fib(n-2).當n=5n=5時,計算fib(5)的函式呼叫情況如下圖所示:

圖片描述

在計算fib(5)時,fib(5)呼叫1次,fib(4)呼叫1次,fib(3)呼叫2次,fib(2)呼叫3次,fib(1)呼叫5次,fib(0)呼叫3次,一共呼叫函式fib()15次。由此,我們可以看到,在計算fib(5)時,存在多次重複的fib()函式的呼叫,當n增大時,重複呼叫的次數會急劇增加,如計算fib(50)時,fib(1)和fib(0)大約會被呼叫2.4×10102.4×1010次。由此可見,該演算法的效率並不是很高,因為該演算法的執行時間是指數時間。
  我們用Python實現上述演算法,並計算f(38)的值及運算時間。Python程式碼如下:

import time# recursive methoddef rec_fib(n):
    if n 

輸出結果如下:

結果:39088169, 執行時間:22.93831205368042

動態規劃法求解

  在使用遞迴法來求解斐波那契數列的第n項時,我們看到了遞迴法的不足之處,因為遞迴法在使用過程中存在大量重複的函式呼叫,因此,效率很差,執行時間為指數時間。為了解決遞迴法存在的問題,我們可以嘗試動態規劃法,因為動態規劃法會在執行過程中,儲存上一個子問題的解,從而避免了重複求解子問題。對於求解斐波那契數列的第n項,我們在使用動態規劃法時,需要儲存f(n-1)和f(n-2)的值,犧牲一點記憶體,但是可以顯著地提升執行效率。
  動態規劃法來求解斐波那契數列第n項的虛擬碼如下:

function fib(n)

    var previousFib := 0, currentFib := 1
    
    if n = 0
    return 0
    else if n = 1
    return 1
    
    repeat n−1 times
        var newFib := previousFib + currentFib        previousFib := currentFib        currentFib := newFib        
    return currentFib

在上述虛擬碼中,並沒有存在重複求解問題,只是在每次執行過程中,儲存上兩項的值,再利用公式f(n)=f(n−1)+f(n−2)f(n)=f(n1)+f(n2)來求解第n項的值。用Python實現上述過程,程式碼如下:

import time# bottom up approach of Dynamic Programmingdef dp_fib(n):
    previousFib = 0
    currentFib = 1
    if n 

輸出結果如下:

結果:39088169, 執行時間:0.0

  顯然,使用動態規劃法來求解斐波那契數列第n項的執行效率是很高的,因為,該演算法的時間複雜度為多項式時間。

參考文獻

  1. 演算法導論(第四版)

  2. ~jordicf/Teaching/programming/pdf/IP07_Recursion.pdf

附錄

用遞迴法和動態規劃法來求解該數列的第n項,完整的Python程式碼如下:

# calculate nth item of Fibonacci Sequenceimport time# recursive methoddef rec_fib(n):
    if n 

輸出結果如下:

結果:39088169, 執行時間:22.42628264427185結果:39088169, 執行時間:0.0


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/756/viewspace-2802202/,如需轉載,請註明出處,否則將追究法律責任。

相關文章