演算法導論15 1

2022-07-07 15:30:12 字數 3672 閱讀 8033

動態規劃(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\)個斐波那契數。畫出子問題圖。圖中有多少頂點和邊?

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

一共有\(n\)個節點,\(2n-3\)條邊。

演算法導論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...