1. 程式人生 > 其它 >演算法思想之動態規劃

演算法思想之動態規劃

動態規劃一直被認為是最難理解的一種演算法思想,什麼重疊子問題、動態轉移方程、最優子結構等等,一聽就高深莫測,沒有往下學習下去的動力

一、初識動態規劃

廢話不多說,我們直接先上一個經典的例子。那就是耳熟能詳的斐波那契數列問題。我們先來看一下問題的定義。

斐波那契數列的定義如下: 斐波那契數列指的是這樣一個數列0,1,1,2,3,5,8,13,21,34,55,89,144,..... 它以遞迴的方法來定義: F(0)=0,F(1)=1, F(n)=F(n-1)+F(n-2)(n>=2,n∈N*)

  1. 遞迴解決:

這個例子最直觀的方法就是用遞迴的方式來實現,畢竟斐波那契數列是用遞迴來定義的。我們來看一下程式碼實現。

def fibs(n):
     if n<2:
          return n
     return fibs(n-1)+fibs(n-2)

C#實現

public static int Foo(int i)
        {
            if (i <= 0)
            {
                return 0;
            }
            else if (i > 0 && i <= 2)
            {
                return 1;
            }
            
else { return Foo(i - 1) + Foo(i - 2); } }

這樣是不是很簡單。我們接下來看一下呼叫的遞迴樹。我們以fibs(6)為例。

其中每個結點表示要計算的斐波那契數列的第幾項,我們可以從上圖發現,會出現許多重複計算的問題,比如fib(4)就計算了兩次。這樣就會帶來時間和空間上的消耗,那我們有什麼方式可以避免重複計算的問題。我們可以使用遞迴中的“備忘錄”功能來解決。我們來看一下程式碼如何實現。

二、用動態規劃解決

我們把整個求解過程分為n個階段,每個階段去求解數列對應項的值。我們在解決當前問題時,也就是求解該對應項的值的時候,會依賴過去的狀態,也就是前面幾項的值來計算。比如我們在求解fibs(6)的時候,我們需要用到fibs(5)和fibs(4)這兩項。 我們來定義一個數組,來記錄每項的狀態。我們也叫做狀態轉移矩陣。 按照斐波那契數列的定義:

F(0)=0,F(1)=1
F(n)=F(n-1)+F(n-2) (n>=2)

我們可以看到F(n)的值只與他的前兩個狀態有關。所以我們只要知道他的前兩個狀態,就可以求出F(n)。

  1. 初始化值F(0)=0,F(1)=1,我們直接放入陣列中。
  2. 要想計算F(2),我們需要知道F(0)和F(1),因為上一步已經放入陣列中,我們直接拿來用就好了,然後把F(2)的結果放入陣列中。
  3. 要想計算F(3),我們需要知道F(2)和F(1),因為F(2)和F(1)已經存在數組裡了,我們直接拿來用就好了,然後把F(3)的結果放入陣列中。

....

依此類推,知道計算到n為止。整個狀態轉移矩陣就計算好了。如下圖所示。我們以求解F(5)為例。

下面我們直接看程式碼實現,這樣比較簡單明瞭。

def fibs(n):
     if n<2:
          return n
     dp=[0 for _ in range(n+1)]
     dp[0]=0
     dp[1]=1
     for i in range(2,n+1):
          dp[i]=dp[i-1]+dp[i-2]
     return dp[n]
print(fibs(6))

上面的程式碼是不是很簡潔明瞭。這就是一種用動態規劃來解決問題的思路。我們把問題分解為n個階段,一個階段一個階段去求解。然後通過當前狀態,來求出下一個狀態,動態的往前推進,這是不是還挺形象的