一文弄懂動態規劃(DP Dynamic Programming)下樓梯,國王和金礦,揹包問題,Dijkstra演算法
動態規劃
參考連結
DP
動態規劃是一種分階段求解決策問題的數學思想
題目一
問:下樓梯問題,有一座高度是10級臺階的樓梯,從下往上走,每跨一步只能向上1級或者2級臺階,請問有多少中走法。
思路
剛才這個題目,你每走一步就有兩種走法,暫時不管0級到8級臺階的過程。想要走到10級,必然是從8級或者9級走的。那麼問題來了,如果我們以及0到9級臺階的走法有x種,0到8級臺階有Y種,那0到10級臺階就是X+Y。
公式就是:
F(10) = F(9)+ F(8)
當只有1級臺階的時候只有一種解法,2級臺階的時候有兩種方法。
遞推公式就是: F(1) = 1 F(2) = 2 F(N) = F(N-1)+ F(N-2)
動態規劃的三個概念:
- 最優子結構
- 邊界
- 狀態轉移公式
當只有1級臺階或2級臺階,我們直接得出結果,無需建華,我們就成F(1)F(2)為邊界。
F(N) = F(N-1)+ F(N-2)是階段與階段之間的狀態轉移公式。
求解問題
方法一:遞迴法
但是複雜度很高,因為當中有很多大量的重複計算。複雜度
。具體分析見開頭的連結。
遇到這種情況,我們只需要建立一個雜湊表,在python種建立一個字典就好了。把每次不同引數的計算結果存入雜湊,當遇到相同引數時,再從雜湊表裡面取出,就不會重複計算了。
方法二
感覺紅色箭頭少了個引數。
在以上程式碼中,集合map是一個備忘錄。當每次需要計算F(N)的時候,會首先從map中尋找匹配元素。如果map中存在,就直接返回結果,如果map中不存在,就計算出結果,存入備忘錄中
其實不用對F(N)自頂向下做遞迴運算,可以從下往上算。已知1,2是不是就能求3了。以此類推。
題目二
國王和金礦
問:有一個國家發現了5座金礦,每座金礦的黃金儲量不同,需要參與挖掘的工人數也不同。參與挖礦工人的總數是10人。每座金礦要麼全挖,要麼不挖,不能派出一半人挖取一半金礦。要求用程式求解出,要想得到儘可能多的黃金,應該選擇挖取哪幾座金礦?
解答
最優子結構:
10個人4金礦(第五金礦不挖的時候),10減去挖第五金礦的人數要求然後剩下4金礦(第五金礦挖的時候)。
最終問題:
10個人5金礦的最優選擇。
最優子結構和最終問題的關係:
設幾個變數便於描述:
N:金礦數量
W:工人人數
G[]:金礦黃金含量
P[]:金礦的用工量
關係:
F(5,10) = Max(F(4,10),F(4,10-P[4])+ G [4])
==> F(N,W) = Max(F(N-1,W),F(N-1,W-P[N-1])+G[N-1])
邊界條件:
if N == 1:
if W>= P[0]:
return G[0]
else:
return 0
總結:
和之前一樣,有三種實現方式,簡單遞迴,備忘錄演算法,動態規劃。
方法二:簡單遞迴
把狀態轉移方程式翻譯成遞迴程式,遞迴的結束的條件就是方程式當中的邊界。因為每個狀態有兩個最優子結構,所以遞迴的執行流程類似於一顆高度為N的二叉樹。方法的時間複雜度是 。
方法三:備忘錄演算法
在簡單遞迴的基礎上增加一個HashMap備忘錄,用來儲存中間結果。HashMap的Key是一個包含金礦數N和工人數W的物件,Value是最優選擇獲得的黃金數。方法的時間複雜度和空間複雜度相同,都等同於備忘錄中不同Key的數量。
方法四:動態規劃
規律
However ,當總工人數變成1000人,每個金礦的用工數也相應增加,這時候如何實現最優選擇呢?
可能你覺得還是之前的動態規劃方法。其實是不對的,我們可以來計算一下,
1000工人5個金礦,需要計算1000 * 5 次,需要開闢 1000 單位的空間。
然而我們用之前的簡單遞迴,需要計算2^n次也就是32次,只需要開闢5單位(遞迴深度的空間)。
所以從上面計算可以知道,動態規劃方法的時間和空間都和W成正比,而簡單遞迴卻與W無關,當工人數量很多的時候,動態規劃反而不如遞迴。
(我又一個想法,思路來源於Glibc 中的 qsort() 的實現在這個連結的舉例分析排序函式板塊中,我的思路是這樣的,兩個演算法都可以寫在函式實現上,如果當N特別大的時候,可以選擇動態規劃的方法,而當N不大,而W特別大的時候,且空間有限制,此時就可以讓演算法退化成簡單遞迴。不知道對不對這個思路,如果哪裡考慮的不對的話,請告訴我,萬分感謝)
以上就是漫畫演算法的全部內容。以下是我的補充內容
揹包問題 和迪杰特斯拉(Dijkstra演算法–求圖最短路徑)
揹包問題
如果認真讀了上面的過程,看到這個題目是不是覺得和前面礦工和金礦很像是不是。揹包問題就是,錢和重量,而前面礦工和金礦問題是,錢和人數的限制。
接下來的思路是演算法圖解中關於動態規劃的講解,可以參考看一下。
寫出正確的動態規劃
Dijkstra’s Algorithm(是求從一點出發的最短路徑)
虛擬碼
Data: G, s, dist[], pred[] and
vSet: set of vertices whose shortest path from s is unknown
Algorithm:
dist[] // array of cost of shortest path from s
pred[] // array of predecessor in shortest path from s
dijkstraSSSP(G,source):
| Input graph G, source node
|
| initialise dist[] to all ∞, except dist[source]=0
| initialise pred[] to all -1
| vSet=all vertices of G
| while vSet is not NULL do
| | find s in vSet with minimum dist[s]
| | for each (s,t,w) in edges(G) do
| | relax along (s,t,w)
| | end for
| | vSet=vSet \ {s}
| end while
以上虛擬碼僅供參考作用,喜歡的話,可以研究一下。下面通過一道題目來了解整個過程。
- 根據虛擬碼進行初始化:
- 根據題目的要求,從node 0開始,遍歷與0相連的每條邊的權重,更新列表,pred代表是從哪裡來的,比如,第二列的0,代表從0來到1。(圖中綠色的代表當前遍歷的點)
- 然後遍歷dist中除去0以外的,最小的值,以這個最小值作為起始點,遍歷它連的邊,更新列表。
- 其實就是重複3的動作,先找剩下的最小邊,遍歷它連的邊,更新列表,你可以動手寫一下剩下的內容。當dist發生變化時,pred也要相應的發生變化,畢竟你要記錄最短路徑,當然要記錄這個路徑是從哪裡來的。
演算法複雜度分析
每條邊都需要遍歷一邊,O(E)
外迴圈是遍歷所有點,則是O(V)
“find s in vSet with minimum dist[s]” 這段程式碼
嘗試找s in vSet,cost為O(V)
==>overall cost 為
如果用優先佇列來實現找最小距離,那麼
Overall cost =