一文弄懂動態規劃(DP Dynamic Programming)下樓梯,國王和金礦,揹包問題,Dijkstra演算法
動態規劃
參考連結
ofollow,noindex">漫畫演算法,什麼是動態規劃?
DP
動態規劃是一種分階段求解決策問題的數學思想
題目一
問:下樓梯問題,有一座高度是10級臺階的樓梯,從下往上走,每跨一步只能向上1級或者2級臺階,請問有多少中走法。
思路
剛才這個題目,你每走一步就有兩種走法,暫時不管0級到8級臺階的過程。想要走到10級,必然是從8級或者9級走的。那麼問題來了,如果我們以及0到9級臺階的走法有x種,0到8級臺階有Y種,那0到10級臺階就是X+Y。

image
公式就是: F(10) = F(9)+ F(8)
當只有1級臺階的時候只有一種解法,2級臺階的時候有兩種方法。
遞推公式就是: F(1) = 1 F(2) = 2 F(N) = F(N-1)+ F(N-2)
動態規劃的三個概念:
- 最優子結構
- 邊界
- 狀態轉移公式

image
當只有1級臺階或2級臺階,我們直接得出結果,無需建華,我們就成F(1)F(2)為邊界。
F(N) = F(N-1)+ F(N-2)是階段與階段之間的狀態轉移公式。
求解問題
方法一:遞迴法

image
但是複雜度很高,因為當中有很多大量的重複計算。複雜度
。具體分析見開頭的連結。
遇到這種情況,我們只需要建立一個雜湊表,在python種建立一個字典就好了。把每次不同引數的計算結果存入雜湊,當遇到相同引數時,再從雜湊表裡面取出,就不會重複計算了。
方法二

image
感覺紅色箭頭少了個引數。
在以上程式碼中,集合map是一個備忘錄。當每次需要計算F(N)的時候,會首先從map中尋找匹配元素。如果map中存在,就直接返回結果,如果map中不存在,就計算出結果,存入備忘錄中

image

image
其實不用對F(N)自頂向下做遞迴運算,可以從下往上算。已知1,2是不是就能求3了。以此類推。

image

image

image
題目二
國王和金礦
問:有一個國家發現了5座金礦,每座金礦的黃金儲量不同,需要參與挖掘的工人數也不同。參與挖礦工人的總數是10人。每座金礦要麼全挖,要麼不挖,不能派出一半人挖取一半金礦。要求用程式求解出,要想得到儘可能多的黃金,應該選擇挖取哪幾座金礦?
解答

image
最優子結構:
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
總結:

image
和之前一樣,有三種實現方式,簡單遞迴,備忘錄演算法,動態規劃。
方法二:簡單遞迴
把狀態轉移方程式翻譯成遞迴程式,遞迴的結束的條件就是方程式當中的邊界。因為每個狀態有兩個最優子結構,所以遞迴的執行流程類似於一顆高度為N的二叉樹。方法的時間複雜度是 。
方法三:備忘錄演算法
在簡單遞迴的基礎上增加一個HashMap備忘錄,用來儲存中間結果。HashMap的Key是一個包含金礦數N和工人數W的物件,Value是最優選擇獲得的黃金數。方法的時間複雜度和空間複雜度相同,都等同於備忘錄中不同Key的數量。
方法四:動態規劃

在這裡插入圖片描述

image

image

image

image
規律

image

image

image

在這裡插入圖片描述
However ,當總工人數變成1000人,每個金礦的用工數也相應增加,這時候如何實現最優選擇呢?

image
可能你覺得還是之前的動態規劃方法。其實是不對的,我們可以來計算一下,
1000工人5個金礦,需要計算1000 * 5 次,需要開闢 1000 單位的空間。
然而我們用之前的簡單遞迴,需要計算2^n次也就是32次,只需要開闢5單位(遞迴深度的空間)。
所以從上面計算可以知道,動態規劃方法的時間和空間都和W成正比,而簡單遞迴卻與W無關,當工人數量很多的時候,動態規劃反而不如遞迴。
(我又一個想法,思路來源於Glibc 中的 qsort() 的實現 在這個連結的舉例分析排序函式板塊中,我的思路是這樣的,兩個演算法都可以寫在函式實現上,如果當N特別大的時候,可以選擇動態規劃的方法,而當N不大,而W特別大的時候,且空間有限制,此時就可以讓演算法退化成簡單遞迴。不知道對不對這個思路,如果哪裡考慮的不對的話,請告訴我,萬分感謝)
以上就是漫畫演算法的全部內容。以下是我的補充內容
揹包問題 和迪傑特斯拉(Dijkstra演算法--求圖最短路徑)
揹包問題

image
如果認真讀了上面的過程,看到這個題目是不是覺得和前面礦工和金礦很像是不是。揹包問題就是,錢和重量,而前面礦工和金礦問題是,錢和人數的限制。
接下來的思路是演算法圖解中關於動態規劃的講解,可以參考看一下。

image

image

image

image

image

image

image

image

image

image

image

image

image

image

image

image

image

image

image

image

image

image

image

image

image

image

image
寫出正確的動態規劃

image

image

在這裡插入圖片描述

image

image

image

image

image

image

image

image

image
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
以上虛擬碼僅供參考作用,喜歡的話,可以研究一下。下面通過一道題目來了解整個過程。

image
-
根據虛擬碼進行初始化:
image
- 根據題目的要求,從node 0開始,遍歷與0相連的每條邊的權重,更新列表,pred代表是從哪裡來的,比如,第二列的0,代表從0來到1。 (圖中綠色的代表當前遍歷的點)
image
-
然後遍歷dist中除去0以外的,最小的值,以這個最小值作為起始點,遍歷它連的邊,更新列表。
image
-
其實就是重複3的動作,先找剩下的最小邊,遍歷它連的邊,更新列表,你可以動手寫一下剩下的內容。當dist發生變化時,pred也要相應的發生變化,畢竟你要記錄最短路徑,當然要記錄這個路徑是從哪裡來的。
image
演算法複雜度分析
每條邊都需要遍歷一邊,O(E)
外迴圈是遍歷所有點,則是O(V)
"find s in vSet with minimum dist[s]" 這段程式碼
嘗試找s in vSet,cost為O(V)
==>overall cost 為如果用優先佇列來實現找最小距離,那麼
Overall cost =