動態規劃(dynamic programming),描述了它區別與其他演算法的最大特徵;
其中\(programming\)表示的是一種**法,在動態規劃演算法執行的過程中,會動態地去將子問題的解填入**和讀取已知子問題的解,以減少不必要的運算;
相同點:
都是通過組合子問題的解來求解原問題
不同點:
動態規劃子問題有重疊,分治法沒有
動態規劃會快取以求解的子問題的解
動態規劃常用於求解最優化問題
刻畫乙個最優解的結構特徵
遞迴地定義最優解的值
計算最優解的值,通常採用自底向上的方法
利用計算出的資訊構造乙個最優解
上面抽象地講述了動態規劃的特徵和設計步驟,但是缺乏細節,下面利用鋼條切割的例子加深理解。
首先需要刻畫最優解的結構特徵,以\(n=4\)為例,一共有下面\(8\)個解:
那麼\(r_n\)的最優解結構如下:
\[r_n = max(p_n, r_1+r_,r_2+r_,...,r_+r_1)
\]然後遞迴地定義最優解的值,要求解\(r_n\)的最優值,需要遞迴地求解\(r_1,r_2,..,r_\)的最優值;
最後計算最優解的值,這裡有兩種方法,帶備忘的自頂向下法和自底向上法。
帶備忘的自頂向下法這種方法實際上是原本自頂向下的改良,原本自頂向下對於每個輸入都會從頭開始計算,比如分別輸入\(8\)和\(9\),會遞迴\(8\)次和\(9\)次,並不會在計算\(9\)的時候復用\(8\)的結果;
帶備忘的自頂向下法,就將已知的輸入的結果保留下來,減少了不必要的計算。
memoized-cut-rod(p, n)
let r[0..n] be a new array
for i = 0 to n
r[i] = -∞
return memoized-cut-rod-aux(p, n, r)
memoized-cut-rod-aux(p, n, r)
if r[n] >= 0
return r[n]
if n == 0
q = 0
else q = -∞
for i = 1 to n
q = max(q, p[i] + memoized-cut-rod-aux(p, n-i, r))
r[n] = q
return q
自底向上法這種方法更加符合邏輯,在解決問題之前先將所有的子問題解決,然後直接得到答案。
bottom-up-cut-rod(p, n)
let r[0..n] be a new array
r[0] = 0
for j = 1 to n
q = -∞
for i = 1 to j
q = max(q, p[i] + r[j-1])
r[j] = q
return r[n]
這兩種方法,時間複雜度相同,但是自頂向下是遞迴求解,函式呼叫後者較高的開銷,所以有著較大的係數;
由公式\((15.3)\)和初始條件\(t(0)=1\) ,證明公式\((15.4)\)成立。公式\(15.3\):
\[t(n) = 1 + \sum_^t(j)
\]公式\(15.4\):
\[t(n) = 2^n
\]令\(s(n) = \sum_^ t(j)\)
\[\begin
&\therefore t(n) = 1 + s(n-1)\\[2ex]
&\therefore t(n-1) = 1 + s(n-2)\\[2ex]
&\therefore t(n) - t(n-1) = s(n-1) - s(n-2) = t(n-1)\\[2ex]
&\therefore t(n) = 2t(n-1)\\[2ex]
&\text t(0) = 1, \therefore t(n) = 2^n
&\end
\]
舉反例證明下面的「貪心」策略不能保證總是得到最有切割方案。定義長度為\(i\)的鋼條密度為\(p_i / i\),即每英吋的價值。貪心策略將長度為\(n\)的鋼條切割下長度為\(i(1\le i \le n)\)的一段,其密度最高。接下來繼續使用相同的策略切割長度為\(n-i\)的剩餘部分。當長度\(n = 4\)時,按照「貪心」策略則切割成長度為\(1\)和\(3\)的鋼條(\(p = 1 + 8 = 9\));而最優解為切割成\(2\)條長度為\(2\)的鋼條(\(p = 5 + 5 = 10 > 9\))。
我們對鋼條切割問題進行一點修改,除了切割下的鋼條段具有不同的**\(p_i\)外,每次切割還要付出固定的成本\(c\)。這樣,切割方案的收益就等於鋼條段的**之和減去切割的成本。設計乙個動態規劃演算法解決修改後的鋼條切割問題。bottom_up_cut_rod_cost(p, n, c):
let r[0...n] and m[0...n] be new arrays
r[0] = 0, m[0] = 0
for i = 1 to n
q = -∞
for j = 1 to i
if q < p[j] + r[i-j] - m[i-j]*c
q = p[j] + r[i-j] - m[i-j]*c
m[i] = m[i-j] + 1
r[i] = q
return r[n]
修改\(memoized-cut-rod\),使之不僅返回最優收益值,還返回切割方案。cut_way(s, n):
i = 0
while n > 0
t[i] = s[n]
n = n - s[n]
i = i + 1
return t[0...i-1]
斐波那契數列可以用遞迴式\((3.22)\)定義。設計乙個\(o(n)\)時間的動態規劃演算法計算第\(n\)個斐波那契數。畫出子問題圖。圖中有多少頂點和邊?一共有\(n\)個節點,\(2n-3\)條邊。fibonacci(n)
if n <=2
return 1
x = 1
y = 1
for i = 3 to n
result = x + y
x = y
y = res
return res
演算法導論15 1鋼條切割 練習總結
15.1 1 由公式 15.3 和初始條件t 0 1,證明公式 15.4 成立。15.1 2 舉反例證明下面的 貪心 策略不能保證總是得到最優切割方案。定義長度為i的鋼條的密度為pi i,即每英吋的價值。貪心策略將長度為n的鋼條切割下長度為i 1 i n 的一段,其密度最高。接下來繼續使用相同的策略...
演算法導論 15 1裝配線排程
總共有n個裝配站 底盤進入到裝配線1和裝配線2的時間記錄在二元陣列e 2 上 底盤在裝配線1和裝配線2上每個站的時間記錄在陣列a1 n 和a2 n 上 底盤在每個站上換裝配線的時間記錄在t1 n 1 t2 n 2 上 在最後乙個站時候,不需要換裝配線了,所以陣列只有n 1個資料 底盤離開裝配線的時間...
演算法導論P151 二叉查詢樹
標頭檔案 bstree.h cpp檔案 bstree.cpp 測試檔案 mainentry.cpp include bstree.h int main int argc,char argv cout endl cout inorder bstree endl inorderbstree root t...