多階段決策問題中,各個階段採起的決策,通常來講是與時間有關的,決策依賴於當前狀態,又隨即引發狀態的轉移,一個決策序列就是在變化的狀態中產生出來的,故有「動態」的含義,稱這種解決多階段決策最優化問題的方法爲動態規劃方法。算法
設計動態規劃具體要知足如下三個條件:數組
如圖,從9開始往下走,每次只能走相鄰的兩個節點,問如何走才能使數字之和最大化。測試
這個問題若是採用遍歷的方式那麼路徑將會呈指數級別增加,計算量很是大
20 x 21 x 22 ....x 2n-1 = 2 n(n-1) / 2
優化
若是採用貪婪算法每次選擇最大值 ,到達第四層的時候:9+15+8+9 < 9+12+10+18。這種方式只能獲得局部最優解,而沒法獲得全局最優解。spa
顯然這兩種方式都不是最優的解決方式。若是採用DP(動態規劃)能夠有效解決。設計
具體思想:咱們從最底層往上走,從5五層到第四層開始比較,選擇最大的值:分別爲19+2,18+10,9+10,5+16.而後基於這4個值繼續比較從第4層往第三層走,最後第三層變爲18+10+10,18+10+6,5+16+8.按照這種思路依次走到第一層。3d
接下來由第4層往第3層走 分別選出最大的方案爲28+10,29+6,21+8,如圖:code
由第3層往第2層 分別選出最大的方案爲38+12,34+15,如圖:xml
由第2層往第1層,可選出38+12,34+15 如圖:blog
最優方案 如圖:
這樣保證了,從第5層開始,每一次抉擇後的結構都是最優的結果,而且不再會收到其餘因素的音響值不會改變,且有由小到大最終每個點的連線其實都是自他開始往下的最優解。
恰好知足了DP算法的三個特性:
1.最優化原理 2.無後向性 3子問題重疊
測試數據和代碼以下:
int array[5][5] = { 7,0,0,0,0, 3,8,0,0,0, 8,1,0,0,0, 2,7,4,4,0, 4,5,2,6,5, }; //動態劃分 for (int i = 4 - 1; i >= 0; i--) { for (int j = 0; j <= 4; j++) { array[i][j] = MAX(array[i + 1][j] , array[i+1][j+1]) + array[i][j]; } } NSLog(@"結果%d",array[0][0]);
有編號分別爲a,b,c,d,e的五件物品,它們的重量分別是2,2,6,5,4,它們的價值分別是6,3,5,4,6,如今給你個承重爲10的揹包,如何讓揹包裏裝入的物品具備最大的價值總和?
DP思想以下:拆分結構,尋找最優子元素。 最大結構是5個物品中尋找重量和爲10且價值最大的物品,那麼最優子結構就是從1個物品選出重量和爲1且價值最大的元素,若是條件知足則就是他自己,不知足則記爲0,而後再一次往上遞增。
含義 | name | weight | value | 1kg | 2kg | 3kg | 4kg | 5kg | 6kg | 7kg | 8kg | 9kg | 10kg |
abcde可選 | a | 2 | 6 | 0 | 6 | 6 | 9 | 9 | 12 | 12 | 15 | 15 | 15(結束) |
bcde可選 | b | 2 | 3 | 0 | 3 | 3 | 6 | 6 | 9 | 9 | 9 | 10 | 11 |
cde可選 | c | 6 | 5 | 0 | 0 | 0 | 6 | 6 | 6 | 6 | 6 | 10 | 11 |
de可選 | d | 5 | 4 | 0 | 0 | 0 | 6 | 6 | 6 | 6 | 6 | 10 | 10 |
e可選 | e | 4 | 6 | 0(開始) | 0 | 0 | 6 | 6 | 6 | 6 | 6 | 6 | 6 |
表格填寫的順序是從左下開始填寫直到右上結束,若是這個表格你能手動填寫完,那麼你就已經學會了01揹包規劃方案。
爲了方便理解,咱們選擇幾個典型的進行描述:
表格從白色部分開始數
第5行第2列(e2):可選物只有e,且有一個負重爲2的揹包,揹包的最大價值爲0,由於e自己的重量爲4,放不下,這個表格故填0.
第2行第2列(b2):可選物爲b,c,d,e,且有一個負重爲2的揹包,揹包最大價值爲3,由於物品b重量爲2恰好能夠放下且價值爲3,表格填3.
第1行第2列(a2):可選物爲a,b,c,d,e,且有一個負重爲2的揹包,揹包最大價值爲6,a和b均可以放入揹包,且a的價值更大,選擇a,表格填6.
第1行第4列(a4):可選物a,b,c,d,e, 且有一個負重爲4的揹包,a能夠裝下,那麼到底裝不裝如a呢?咱們須要作一個比較,假如裝下a,揹包剩餘負重2,可選物爲b,c,d,e, (b2)+6=9大於b4,選擇裝入a更好,因此a4的填入9。
第1行第5列(a6):裝入a後剩餘重量爲4,可選b,c,d,e. b(4)+6=12 > b6因此填入12.
若
f[i,j]表示在前i件物品中選擇若干件放在承重爲 j 的揹包中,能夠取得的最大價值。Pi表示第i件物品的價值,Wi表示第i件物品的重量,01揹包核心方程式爲:
f[i,j] = Max{ f[i-1,j-Wi] + Pi( j >= Wi ) , f[i-1,j] }
核心代碼以下:
- (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. //揹包能裝入的總重量爲10 5個物品的重量分別爲2,2,6,5,4 價值爲6,3,5,4,9 int knapsackSize = 10; MyItem * item1 = [MyItem myitemWithWeight:2 value:6]; MyItem * item2 = [MyItem myitemWithWeight:2 value:3]; MyItem * item3 = [MyItem myitemWithWeight:6 value:5]; MyItem * item4 = [MyItem myitemWithWeight:5 value:4]; MyItem * item5 = [MyItem myitemWithWeight:4 value:9]; NSArray* myitems = @[item1,item2,item3,item4,item5]; int value = [self getValueByKnapsack:myitems knapsackSize:knapsackSize]; NSLog(@"揹包可裝入的最大價值%d",value); } //計算01揹包能裝入的價格最優的值 MyItem(有兩個屬性value和weight) 能夠用於被裝的元素 size揹包一共能載重多少 -(int)getValueByKnapsack:(NSArray<MyItem *> *)myitems knapsackSize:(int)knapsackSize { //初始化一個 二維表格記錄每一個最優策略的值 空間換時間 行數爲總元素個數 列數爲揹包從0開始到總重量數 int ** a; a = (int **)malloc(sizeof(int *) * myitems.count); for (int i = 0; i < myitems.count; i++) { a[i] = (int *)malloc(sizeof(int) * knapsackSize + 1); a[i][0] = 0; } //最優子元素從只裝1個元素且載重只爲1開始計算,保證最優子元素且無後向性 //遍歷重量從假如揹包只能載重1的策略開始 for (int i = 1; i <= knapsackSize; i ++) { //可選物品從 0 到 全部 for (int j = 0; j < myitems.count; j++) { MyItem * item = myitems[j]; if (i < item.weight) { //揹包裝不下的狀況 if (j == 0) { //只有一個可選數據時 a[j][i] = 0; } else { //有多個可選數據 則使用上一個最優策略 a[j][i] = a[j-1][i]; } } else { //揹包裝的下的狀況 if (j == 0) { //只有一個可選數據 這個數據記錄爲最優策略 a[j][i] = item.value; } else { //有多個可選擇的物品 則和上一個最優策略比較選擇最優策略 a[j][i] = MAX(a[j - 1][i], item.value + a[j - 1][i - item.weight]); } } } } //查詢最大值 int maxValue = 0; for (int i = 1; i <= knapsackSize; i ++) { for (int j = 0; j < myitems.count; j ++) { if (a[j][i] > maxValue) { maxValue = a[j][i]; } } } return maxValue; }
求從1號點開始出發到後面全部點的最短路徑。
爲了跟直觀的讓計算機來表示這組路徑,咱們能夠把他轉化爲一個二維數組。
表示每一個點到其餘點的距離,沒法直接到達經過∞表示
DP思想以下:
要求1到全部點的位置都是最最短路徑,那麼計算出來了後1隨便指定一個點都確定是最優的,一樣在這個大組合中先拆分爲子元素,子元素就是1到任意一個指定點好比1-二、1-3等等,而後從最小子元素開始算起。
用一個1位數組dis來表示1到各個點的路程,初始以下:
這個最小的子元素就是1-2,由於2號是離1最近的點,再沒有誰比他更近了,那麼dis[2]的值就成了肯定了,之後再不會收誰的影響而改變了,1-2的距離等於1確定是最短的路徑。
既然已經選擇了2,那麼再看看接下來從2號能到哪裏呢,有2-3,2-4. 仍是和之前同樣開始比較看看誰纔是最優解dis[2] + e[2][3] = 1 + 9 < dis[3], 1-2在2-3的方式比直接從1-3更優,所以dis[3] 更新爲10,這個過程叫作「鬆弛」,1好到3號的路程就是dis[3],經過2-3鬆弛成功。這就是迪傑斯特拉核心思想:經過邊來鬆弛各個路程。一樣dis[2] + e[2][4] = 4 < dis[4],所以dis[4]更新爲4,鬆弛後的dis數組變爲:
接下來繼續從剩下的3,4,5,6中選出距離1最近的點進比較。3,4,5,6最近的是4,dis[4]的值變成了肯定值. 4能夠通過的路線有4-3,4-5,4-6.按照以前的方案新一輪鬆弛以後dis數組變爲:
接下來從剩下的3,5,6中選出距離1最近的點,點3。dis[3]變成了肯定了,3有3-5。鬆弛後變爲:
接下來從5,6中選出5,有5-6,鬆弛後變爲:
最後還有6,鬆弛後變爲:
最終這個鬆弛後的Dis數組就是從1到各個點的最佳路徑。
總結一下就是:每次知道離原點(上面例子就是1)最近的一個點,而後以該點爲中心進行鬆弛,知道全部的點都走完一個輪詢,最終獲得全部離原點最近的點。
核心代碼以下:
//構建鄰接矩陣 實際上爲6,6 爲了方便顯示全部從1~6開始計算 int matrix[7][7] = { 0,0,0,0,0,0,0, 0,0,1,12,999,999,999, 0,999,0,9,3,999,999, 0,999,999,0,999,5,999, 0,999,999,4,0,13,15, 0,999,999,999,999,0,4, 0,999,999,999,999,999,0 }; int book[7] = {0,0,0,0,0,0,0}; //記錄已經處理過的頂點 int dis[7] = {0,0,1,12,999,999,999}; //最佳路徑 默認是1到全部點的距離,999爲無線大距離 注: 從1開始計算 int u = 0; for (int i = 1; i<=6; i++) { //表示查找次數 int min = 999; //尋找距離頂點1最近的點,且爲鬆弛的點 for (int j = 1; j <=6; j++) { if (book[j] == 0 && dis[j] < min) { min = dis[j]; u = j; } } book[u] = 1; //標誌U目前是距離1點最近且未處理的點, 立刻要用於處理 for (int k = 1; k <= 6 ; k++) { //查找U點能夠到達的路權,且比較計算最優的dis if (matrix[u][k] < 999) { if (dis[u] + matrix[u][k] < dis[k]) { //若是1點到U點+U點到K點的距離 < dis[k]的距離,則更新最優距離 dis[k] = dis[u] + matrix[u][k]; } } } }
最後再拓展一個小問題,你能夠先不看思路嘗試着本身用動態劃分的思想解決。
對於一個從1到N的連續整數集合{1,2,3......,n-1,n},劃分爲兩個子集,保證兩個集合的和相等。
例:n=3可分爲{1,2}and{3}. 若是n=7可分爲 {1,6,7} and {2,3,4,5} {2,5,7} and {1,3,4,6} {3,4,7} and {1,2,5,6} {1,2,4,7} and {3,5,6}
設計一個程序 輸入任意數劃分出可行的方案數,不能劃分則輸出0.
思路以下:
1+2+3.....+n = n*(n+1)/2, 兩個相同的子集任意一個的和確定爲總數和的一半,顧和必定爲n*(n+1)/4,計做f(n). 因此這個題能夠轉化爲從集合中找出和爲f(n)的子集合的數量,將他除以2就是咱們能夠獲得的劃分方案。 這樣又能夠用01揹包的思想再次轉換,就成了揹包問題了。物品1,2,3......,n, 價值分別爲1,2,3......,n.給定一個稱重爲f(n)的揹包,問一共有多少種方案讓其恰好放滿,最後將方案除以2就是真確結果。
表格劃分以下: