算法學習筆記(八) 動態規劃的通常求解方法

1 一個問題:換零錢方式的統計

SICP 第一章 1.2.2 樹形遞歸中,有這麼一問題:給了半美圓,四分之中的一個美圓。10美分,5美分和1美分的硬幣。將1美圓換成零錢,一共同擁有多少種不一樣方式?更通常的問題是,給定了隨意數量的現金,咱們能寫一個程序,計算出所有換零錢方式的種數嗎?python


2 動態規劃的基本模型

動態規劃(Dynamic programming,DP),是研究一類最優化問題的方法,經過把原問題分解爲相對簡單的子問題的方式求解複雜問題。動態規劃處理的也就是是多階段決策最優化問題,這一類問題可將過程分紅若干個互相聯繫的階段,在每一階段都做出決策,從而使整個過程達到最好的結果。

所以各個階段決策的選取不能隨意肯定,它依賴於當前面臨的狀態。又影響之後的發展。當各個階段決策肯定後,就組成一個決策序列,從而也就肯定了整個過程的一條活動路線。這樣的把一個問題看作是一個先後關聯具備鏈狀結構的多階段過程稱爲多階段決策過程。算法

動態規劃著名的應用實例有:求解最短路徑問題,揹包問題,項目管理,網絡流優化等。動態規劃的基本模型例如如下:
網絡

  1. 肯定問題的決策對象
  2. 對決策過程劃分階段
  3. 對各階段肯定狀態變量
  4. 依據狀態變量肯定費用函數和目標函數
  5. 創建各階段狀態變量的轉移過程。肯定狀態轉移方程

3 使用動態規劃的通常前提

3.1 知足動態規劃的最優化原理

做爲整個過程的最優策略具備例如如下性質:無論過去的狀態和決策怎樣,對前面的決策所造成的當前狀態而言,餘下的諸決策必須構成最優策略。


通俗理解就是子問題的局部最優將致使整個問題的全局最優,即問題具備最優子結構的性質,也就是說一個問題的最優解僅僅取決於其子問題的最優解,非最優解對問題的求解沒有影響。函數


3.2 知足動態規劃的無後效性原則

所謂無後效性原則。指的是這樣一種性質:某階段的狀態一旦肯定,則此後過程的演變再也不受此前各狀態及決策的影響。也就是說,「將來與過去無關」,當前的狀態是此前歷史的一個完整總結,此前的歷史僅僅能經過當前的狀態去影響過程將來的演變。

詳細地說,假設一個問題被劃分各個階段以後,階段 I 中的狀態僅僅能由階段 I+1 中的狀態經過狀態轉移方程得來,與其它狀態沒有關係,特別是與未發生的狀態沒有關係,這就是無後效性。從圖論的角度去考慮。假設把這個問題中的狀態定義成圖中的頂點,兩個狀態之間的轉移定義爲邊,轉移過程當中的權值增量定義爲邊的權值,則構成一個有向無環加權圖,所以,這個圖可以進行「拓撲排序」,至少可以按他們拓撲排序的順序去劃分階段。優化


4 動態規劃設計方法

4.1 通常方法

通常 由初始狀態開始。經過對中間階段決策的選擇,達到結束狀態。這些決策造成了一個決策序列,同一時候肯定了完畢整個過程的一條活動路線。步驟爲:
  1. 劃分階段:依照問題的時間或空間特徵,把問題分爲若干個階段。在劃分階段時。注意劃分後的階段必定要是有序的或者是可排序的。不然問題就沒法求解。
  2. 肯定狀態和狀態變量:將問題發展到各個階段時所處於的各類客觀狀況用不一樣的狀態表示出來。固然,狀態的選擇要知足無後效性。

  3. 肯定決策並寫出狀態轉移方程:因爲決策和狀態轉移有着自然的聯繫。狀態轉移就是依據上一階段的狀態和決策來導出本階段的狀態。

    因此假設肯定了決策,狀態轉移方程也就可寫出。但其實常常是反過來作。依據相鄰兩段各狀態之間的關係來肯定決策。this

  4. 尋找邊界條件:給出的狀態轉移方程是一個遞推式,需要一個遞推的終止條件或邊界條件。
  5. 程序設計實現:動態規劃的主要難點在於理論上的設計。一旦設計完畢,實現部分就會很easy。

4.2 逆向推導

逆向思惟法是指從問題目標狀態出發倒推回初始狀態或邊界狀態的思惟方法。假設原問題可以分解成幾個本質一樣、規模較小的問題,很是天然就會聯想到從逆向思惟的角度尋求問題的解決。動態規劃與分治法最大的不一樣在於分解出來的各個子問題的性質不一樣:
  • 分治法要求各個子問題是獨立的(即不包括公共的子問題),所以一旦遞歸地求出各個子問題的解後,即可自下而上地將子問題的解合併成原問題的解。假設各子問題是不獨立的,那麼分治法就要作不少沒必要要的工做,反覆地解公共的子問題。
  • 動態規劃與分治法的不一樣之處在於動態規劃贊成這些子問題不獨立(即各子問題可包括公共的子問題),它對每個子問題僅僅解一次,並將結果保存起來,避免每次碰到時都要反覆計算。這就是動態規劃高效的一個緣由。
動態規劃的逆向推導步驟:
  1. 分析最優值的結構。刻畫其結構特徵;
  2. 遞歸地定義最優值;
  3. 按自底向上或自頂向下記憶化的方式計算最優值;

4.3 正向推導

正向思惟法是指從初始狀態或邊界狀態出發。利用某種規則不斷到達新的狀態,直到問題目標狀態的方法。

動態規劃的正向思惟法。正是從已知最優值的初始狀態或邊界狀態開始,依照必定的次序遍歷整個狀態空間,遞推出每個狀態所相應問題的最優值。spa


正向思惟法中。再也不區分原問題和子問題,將動態規劃的過程當作是從狀態到狀態的轉移。將所有的狀態構造出一個狀態空間,並在狀態空間中設想一個狀態網絡。若對兩個狀態i,j,存在決策變量di使t(i,di)=j。則向狀態網絡加入有向邊。.net

給定己知最優值的初始狀態或邊界狀態,可以沿著有向邊推廣到未知最優值的新狀態。利用狀態轉移方程獲得新狀態的狀態變量的最優值。設計

咱們可以用這樣的方式遍歷整個狀態空間,獲得每個狀態的狀態變量的最優值。
動態規劃的正向推導步驟:
code

  1. 構造狀態網絡;
  2. 依據狀態轉移關係和狀態轉移方程創建最優值的遞推計算式:
  3. 按階段的前後次序計算每個狀態的最優值;

動態規劃需要按階段遍歷整個狀態空間。所以動態規劃的效率取決於狀態空間的大小和計算每個狀態最優值的開銷:假設狀態空間的大小是多項式的,那麼應用動態規劃的算法就是多項式時間的;假設狀態空間的大小是指數的,那麼應用動態規劃的算法也是指數時間的。所以,找一個好的狀態劃分對動態規劃的效率是相當重要的。


5 小實驗換零錢問題求解

逆推狀態轉移方程:數量爲 a 的錢換成 n 種硬幣的不一樣方式等於:
  • 數量爲 a 的錢換成除第一種硬幣外的 n-1 種硬幣(必不包括第一種硬幣)的不一樣方式數目加上,
  • 數量爲 a-d 的錢(必包括第一種硬幣)換成所有硬幣種類的不一樣方式數目。d 爲第一種硬幣幣值
逆推到初始狀態:
  • 假設錢爲 0 ,說明正好換完成,是一種換零錢方法,
  • 假設錢爲負數,或者種類已經遞歸到 0 種,則說明沒有正好換完,不是一種換法,返回0;

還可以正向推導。打表記錄已經計算出的值。

Python實現

NUM = 0


def count_change(amount, money, kinds):
    ''' 樹形遞歸存在冗餘'''
    global NUM
    if amount == 0:
        NUM+=1
        return 1
    if amount < 0 or kinds == 0:
        NUM+=1
        return 0
    NUM+=1
    return count_change(amount, money, kinds - 1) + count_change(amount - money[kinds - 1], money, kinds)

def count_dy(amount,money,kinds):
    '''動態規劃,打表記錄已經計算的值'''
    table = [[0 for col in range(kinds)] for row in range(amount+1)]
    table[0] = [1]*kinds
    for i in range(1,amount+1):
        for j in range(kinds):
            # 包括 money[j]
            x = table[i - money[j]][j] if i-money[j] >= 0 else 0
            # 不包括 money[j]
            y = table[i][j-1] if j>=1 else 0
            table[i][j] = x+y
    return table[amount][kinds-1]

if __name__ == '__main__':
    money = [1, 5, 10, 25, 50]
    print(count_change(100, money, len(money)),'time:',NUM)
    print(count_dy(100, money, len(money)),'time:',100*len(money))

'''
292 time: 15499
292 time: 500
'''

SICP中的Scheme實現(Racket)

【地址:http://blog.csdn.net/thisinnocence/article/details/41073275】

相關文章
相關標籤/搜索