動態規劃的理解
什麼是動態規劃
動態規劃是一種非常精妙的演算法思想,它沒有固定的寫法,極其靈活,常常需要具體問題具體分析。和之前介紹的大部分演算法不同,一開始就直接討論動態規劃的概念並不是很好的學習方式,反而先接觸一些經典模型會有更好的效果。因此本章主要介紹一些動態規劃的經典模型,並在其中穿插動態規劃的概念,讓讀者慢慢接觸動態規劃。同時請讀者不要畏懼,多訓練,多思考,多總結是學習動態規劃的重點。
動態規劃(dynamic programming, dp) 是一種用來解決一類最優化問題的演算法思想。簡單來說,動態規劃將乙個複雜的問題分解成若干個子問題,通過綜合子問題的最優解來得到原問題的最優解。需要注意的是,動態規劃會將每個求解過的子問題的解記錄下來,這樣當下一次碰到同樣的子問題時,就可以直接使用之前記錄的結果,而不是重複計算。注意:雖然動態規劃採用這種方式來提高計算效率,但不能說這種做法就是動態規劃的核心(後面會說明這一點)。
一般可以使用遞迴或者遞推的寫法來實現動態規劃,其中遞迴寫法在此處又稱作記憶化搜尋。
什麼問題可以使用動態規劃解決
動態規劃的遞推寫法
以經典的數塔問題為例,如圖11-3所示,將一些數字排成數塔的形狀,其中第一層有乙個數字,第二層有兩個數字……第n層有n個數字。現在要從第一層走到第n層,每次只能走向下一層連線的兩個數字中的乙個,問:最後將路徑上所有數字相加後得到的和最大是多少?
令dp[i][j]
表示從第i行第j個數字出發的到達最底層的所有路徑中能得到的最大和,例如dp[3][2]
就是圖中的7到達最底層的路徑最大和。在定義這個陣列之後,dp[1][1]
就是最終想要的答案,現在想辦法求出它。
注意到:dp[i][j] = max(dp[i+1][j],dp[i+1][j+1])+f[i][j]
把dp[i][j
稱為問題的狀態,而把上面的式子稱作狀態轉移方程,可以發現,數塔的最後一層的dp值總是等於元素本身,即dp[n][j] == f[n][j](1<=j<=n)
,把這種可以直接確定其結果的部分稱為邊界,而動態規劃的遞推寫法總是從這些邊界出發,通過狀態轉移方程擴散到整個dp陣列。
這樣就可以從最底層各位置的dp值開始,不斷往上求出每一層各位置的dp值,最後就會得到dp[1][1]
,即為想要的答案。
演算法**
#include
#include
#include
using namespace std;
const
int maxn =
1000
;int f[maxn]
[maxn]
,dp[maxn]
[maxn]
;int
main()
}for
(int j =
1; j <= n ; j++
) dp[n]
[j]= f[n]
[j];
for(
int i = n-
1; i >=
1; i--)}
printf
("%d\n"
,dp[1]
[1])
;// dp[1][1]即為需要的答案
return0;
}
如果乙個問題的最優解可以由其子問題的最優解有效地構造出來,那麼稱這個問題擁有最優子結構(optimal substructure)。最優子結構保證了動態規劃中原問題的最優解可以由子問題的最優解推導出來。因此,乙個問題必須擁有最優子結構,才能使用動態規劃去解決。
至此,重疊子問題和最優子結構的內容已介紹完畢。需要指出,乙個問題必須擁有重疊子問題和最優子結構,才能使用動態規劃去解決。
注意:和分治法,貪心演算法的比較
分治法與動態規劃。分治和動態規劃都是將問題分解為子問題,然後合併子問題的解得到原問題的解。但是不同的是,分治法分解出的子問題是不重疊的,因此分治法解決的問題不擁有重疊子問題,而動態規劃解決的問題擁有重疊子問題。例如,歸併排序和快速排序都是分別處理左序列和右序列,然後將左右序列的結果合併,過程中不出現重疊子問題,因此它們使用的都是分治法。另外,分治法解決的問題不一定是最優化問題,而動態規劃解決的問題一定是最優化問題。
貪心與動態規劃。貪心和動態規劃都要求原問題必須擁有最優子結構。二者的區別在於,貪心法採用的計算方式類似於上面介紹的」自頂而下「,但是並不等待子問題求解完畢後再選擇使用哪乙個,而是通過一種策略直接選擇乙個子問題去求解,沒被選擇的子問題就不去求解了,直接拋棄。也就是說,它總是只在上一步選擇的基礎上繼續選擇,因此整個過程以一種單鏈的流水方式進行,顯然這種所謂"最優選擇"的正確性需要用歸納法證明。例如對數塔問題而言,貪心法從最上層開始,每次選擇左下和右下兩個數字中較大的乙個,一直到最底層得到最後結果,顯然這不一定可以得到最優解。而動態規劃不管是採用自底而上還是自頂而下的計算方式,都是從邊界開始向上得到目標問題的解。也就是說,它總是會考慮所有子問題,並選擇繼承能得到最優結果的那個,對暫時沒被繼承的子問題,由於重疊子問題的存在,後期可能會再次考慮它們,因此還有機會成為全域性最優的一部分,不需要放棄。所以貪心是一種壯士斷腕的決策,只要進行了選擇,就不後悔;動態則要看哪個選擇笑到了最後,暫時領先說明不了什麼。
理解動態規劃
看了演算法導論上對動態規劃的講解,覺得自己對動態規劃的理解又進了一步,之前在讀到 演算法之道 相關章節時就有這感覺,但是仍然不敢說自己已經完全掌握了動態規劃,只是比以前又透徹了一些,說說自己新的理解,其實就是複述一下演算法導論上的內容而已。裝配線排程問題 乙個產品要經過n道工序,有兩條裝配鏈提供著n...
理解動態規劃
通過了解契波那契數列學習動態規劃 問題 斐波那契數列為1 1 2 3 5 8 13 21 34 寫乙個函式,輸入n,求斐波那契 fibonacci 數列的第n項。遞迴方法 includeusing namespace std int dfs int x int main return a x int...
Floyd 動態規劃的理解
這裡我一直好奇的是這三個漂亮的迴圈是怎麼完成最終正確的結果的,如何證明它們是正確的。直到看了這裡的解析,才算弄懂了。floyd演算法是乙個經典的動態規劃演算法。用通俗的語言來描述的話,首先我們的目標是尋找從點i到點j的最短路徑。從動態規劃的角度看問題,我們需要為這個目標重新做乙個詮釋 這個詮釋正是動...