1. 程式人生 > 實用技巧 >演算法(Java實現)—— 動態規劃演算法

演算法(Java實現)—— 動態規劃演算法

動態規劃演算法

應用場景—0-1揹包問題

揹包問題:有一個揹包,容量為4磅,現有物品如下

物品重量價格
吉他(G) 1 1500
音響(S) 4 3000
電腦(L) 3 2000

要求:

  1. 達到目標為裝入的揹包的總價值最大,且重量不超出

  2. 要求裝入的物品不可重複

動態規劃演算法介紹

  1. 動態規劃(Dynamic Programming)演算法的核心思想是:將大問題劃分為小問題進行解決,熊二一步步獲取最優解的處理演算法

  2. 與分治演算法類似,但不同的是動態規劃子問題不相互獨立

  3. 動態規劃可以通過填表的方式來逐步推進,得到最優解

解決0-1揹包問題

主要思想

利用動態規劃來解決,每次遍歷到第i個物品,根據w[i]和v[i]來確定是否需要將該物品放入揹包中。即對於給定的n個物品,令:

  • w[i]:第i個商品的重量

  • val[i]:第i個商品的價值

  • C:揹包容量

  • v[i][j :表示前i個物品中能夠裝入容量為j的揹包中的最大價值

則有下列結論:

v[i][0] = v[0][j] = 0;
當w[i] > j時:v[i][j] = v[i-1][j]
當j >= w[i]時:v[i][j] = max{v[i-1][j],v[i-1][j-w[i]] + val[i]}

思路圖解

揹包的填表過程

  1. 物品還未裝入揹包,初始狀態

    行,0磅,1磅……代表揹包容量,哪一行表示可以放入此行及 以上行的物品,但是哪一行先方哪一行的物品

    列,代表物品在對應揹包容量下各自在揹包中的價格

    物品0磅1磅2磅3磅4磅
    0 0 0 0 0
    吉他(G)
    音響(S)
    電腦(L)
  2. 加入現在只有吉他此時不論揹包容量有多大,只能放一把吉他

    物品0磅1磅2磅3磅4磅
    0 0 0 0 0
    吉他(G) 1500(G) 1500(G) 1500(G) 1500(G)
    音響(S)
    電腦(L)
  3. 假如有吉他和音響,當揹包容量同時滿足多個物品時,考慮哪個物品價值更高將其放入

    物品0磅1磅2磅3磅4磅
    0 0 0 0 0
    吉他(G) 1500(G) 1500(G) 1500(G) 1500(G)
    音響(S) 1500(G)
    1500(G) 1500(G) 3000(S)
    電腦(L)
  4. 假如由吉他,音響,電腦時,先放電腦,放完之後如果有空餘空間可以放入其他物品則放入,否則不用再關心

    物品0磅1磅2磅3磅4磅
    0 0 0 0 0
    吉他(G) 1500(G) 1500(G) 1500(G) 1500(G)
    音響(S) 1500(G) 1500(G) 1500(G) 3000(S)
    電腦(L) 1500(G) 1500(G) 2000(L) 2000(L) + 1500(G)

    則有下列結論:

    //表示填入的表的第一行和第一列置0
    v[i][0] = v[0][j] = 0;

    //當新增加商品時,若新商品大於揹包容量時,則直接使用上一單元格的裝入策略
    當w[i] > j時:v[i][j] = v[i-1][j]

    //當新增加商品時,其容量小於揹包容量,
    //裝入的策略:
    //1. v[i-1][j]上一單元格的價值
    //2. v[i-1][j-w[i]] + v[i]當前商品的 價值+剩餘空間裝入物品價值的最大
    //3. 此時比較裝入商品的價值,使用價值最 大的策略
    當j >= w[i]時:v[i][j] = max{v[i-1][j],v[i-1][j-w[i]] + val[i]}

程式碼實現

package whyAlgorithm.dynamic;

import java.util.Arrays;

/**
* @Description TODO 動態規劃解決0-1揹包問題
* @Author why
* @Date 2020/12/9 21:04
* Version 1.0
**/
public class KnapsackProblem {
public static void main(String[] args) {
int[] w = {1,4,3};//物品重量
int[] val = {1500,2000,3000};//物品價值
int m = 4;//揹包容量
int n = val.length;//物品個數

//為記錄放入商品的情況,定義一個二維陣列
int[][] path = new int[n+1][m+1];

//建立二維陣列
//v[i][j]表示前i個物品能夠裝入容量為j的揹包中最大的價值
int[][] v = new int[n+1][m+1];

//初始化第一行和第一列,在本程式中可以不處理,因為預設為0
for (int i = 0; i < v.length; i++) {
//將第一列置為0
v[i][0] = 0;
//將第一行置為0
v[0][i] = 0;
}
//根據前面的公式動態規劃處理
for (int i = 1; i < v.length; i++) {//不處理第一行
for (int j = 1; j < v[0].length; j++) {//不出來第一列
//公式
//因為i從1開始,故原公式修改為 w[i] = w[i-1]
if (w[i-1] > j){
v[i][j] = v[i-1][j];
}else {
//int b = v[i-1][j-w[i-1]] + val[i-1];
//int max = Math.max(v[i - 1][j], b);
//v[i][j] = max;
//為了記錄商品存放到揹包的情況不能簡單地使用上面的公式,需要使用if,else體現這個公式
if (v[i-1][j] < v[i-1][j-w[i-1]] + val[i-1]){
v[i][j] = v[i-1][j-w[i-1]] + val[i-1];
//記錄當前情況
path[i][j] = 1;
}else {
v[i][j] = v[i-1][j];
}
}
}
}
System.out.println("分配表:");
for (int i = 0; i < v[0].length-1; i++) {
System.out.println(Arrays.toString(v[i]));
}

//輸出放入的哪些商品
//遍歷path,這樣輸出會有誤,我們要最後的放入
// for (int i = 0; i < path.length; i++) {
// for (int j = 0; j < path[0].length; j++) {
// if (path[i][j] == 1){
// System.out.printf("第%s個商品放入到揹包\n",i);
// }
// }
// }

int i = path.length - 1;//行的最大下標
int j = path[0].length - 1;//列的最大下標

while (i > 0 && j > 0){//從path陣列的最後開始找
if (path[i][j] == 1){
System.out.printf("第%s個商品放入到揹包\n",i);
j -= w[i-1];
}
i--;
}
}
}