动态规划算法
应用场景——背包问题
背包问题:有一个背包,容量为4磅,现有如下物品。
物品 | 重量 | 价格 |
---|---|---|
吉他(G) | 1 | 1500 |
音响(S) | 4 | 3000 |
电脑(L) | 3 | 2000 |
- 要求达到的目标为装入的背包的总价值最大,并且重量不超出
- 要求装入的物品不能重复
动态规划算法介绍
1 动态规划(Dynamic Programming) 算法的核心思想是:将大问题划分为小问题进行解决,从而一步步获取最优解决的处理算法
2 动态规划算法与分治算法类似,其基本思想也是将待求解问题分解成诺干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。
3 与分治法不同的是,==适合用于动态规划求解的问题,经分解得到子问题往往不是相互独立的==。(即下一个子阶段的求解是建立在上一个子阶段的解的基础上,进行进一步的求解)
4 动态规划可以通过==填表的方式==来逐步推进,得到最优解。
动态规划算法最佳实践——背包问题
思路分析和图解
- 背包问题主要是指一个给定容量的背包,若干具有一定价值和重量的物品,如何选择物品放入背包使用物品的价值最大。其中又分 01背包^每件物品都不可重复^ 和 完全背包 (完全背包指的是:每种物品都有无限件可用)
这里的问题属于 01背包 ,即每一个物品最多放一个。而==无限背包可以转化为 01背包==。
- 算法的主要思想,利用动态规划来解决。每次遍历到的第i个物品,根据w[i]和v[i]来确定是否需要将物品放入背包中。即对于给定的n个物品,设v[i],w[i]分别为第i个物品的价值和重量,C为背包的容量。再令
v[i][j]
表示在前i个物品中能够装入容量为j的背包中的最大价值。则我们有下面的结果:
图解示意图:
1 v[i][0] = v[0][j] = 0;
表示 填入表 第一行 和 第一列 是0
2 当 w[i - 1] > j
时 ->:v[i][j] = v[i - 1][j]
当准备加入新增的商品的容量大于 当前背包的容量时,就直接使用上一个单元格的装入策略 i - 1是因为程序中i是从1开始的数组下标从0开始。
3 当 j >= w[i]
时:v[i][j] = max {v[i - 1][j],v[i - 1][j - w[i]]}
当准备加入的新增的商品的容量大小等于当前背包 的容量,转入的方式如下:
v[i - 1][j]
:就是上一个单元格的装入的最大值
val[i]
:表示当前商品的价值
v[i - 1][j - w[i]]
:装入 i - 1 商品,到剩余空间 j - w[i] 的最大值
当j >= w[i]
时:v[i][j] = max {v[i - 1][j],val[i] + v[i - 1][j - w[i]]}
;
解释j >= w[i]
公式的含义:如果新的商品装不进去那么就采用原先的v[i][j] = v[i - 1][j]
装入策略。如果商品可以装进去那么就要看哪一个更大v[i][j] = max{v[i - 1][j],val[i] + v[i - 1][j - w[i]]}
是v[i - 1][j]
更大呢还是val[i] + v[i - 1][j - w[i]]
把新加的商品加入以后再去加上剩余空间对应的最大值两个的和看哪个更大,取最大的
代码实现:
@SuppressWarnings("all")
public class KnapsackProblem {
public static void main(String[] args) {
int[] w = {1, 4, 3};//物品的重量
int[] val = {1500, 3000, 2000};//物品的价格
int m = 4;//背包的容量
int n = val.length;//物品的个数
//n+1因为第一行中都是0不算第一行,m+1因为第一列都是0不算第一列
//v[i][j]表示在前i个物品中能够装入容量为j的背包中的最大价值
int[][] v = new int[n + 1][m + 1];
//为了记录放入商品的 情况,我们定义一个二维数组
int[][] path = new int[n + 1][m + 1];
//初始化第一行和第一列,这里在本程序中,可以不去处理,因为默认是0
for (int i = 0; i < v.length; i++)
v[i][0] = 0;//将第一列设置为0
for (int i = 0; i < v[0].length; i++)
v[0][i] = 0;//将第一行设置0
//根据前面得到的公式来动态规划处理i,从1 开始因为第一行不做处理
for (int i = 1; i < v.length; i++)//i +1 第一行不做处理
for (int j = 1; j < v[0].length; j++) {//j +1 第一列不做处理
//公式
if (w[i - 1] > j)//因为程序是i,是从1开始的,因此原来公式中的w[i] 修改成w[i - 1]
v[i][j] = v[i - 1][j];
else {
//说明:
//因为i从1开始的,因此公式需要调整成
//v[i][j] = Math.max(v[i - 1][j], val[i - 1] + v[i - 1][j - w[i - 1]]);
//为了记录商品存放到背包的情况,不能直接的使用上面的公式,需要使用if-else来体现公式
if (v[i - 1][j] < val[i - 1] + v[i - 1][j - w[i - 1]]) {
v[i][j] = val[i - 1] + v[i - 1][j - w[i - 1]];
//吧当前的情况记录到path数组中
path[i][j] = 1;
} else
v[i][j] = v[i - 1][j];
}
}
//输出查看情况
for (int i = 0; i < v.length; i++) {
for (int j = 0; j < v[i].length; j++)
System.out.print(v[i][j] + " ");
System.out.println();
}
System.out.println("================================");
//输出最后我们是放入的哪些商品
//
// for (int i = 0; i < path.length; i++) {
// for (int j = 0; j < path[i].length; j++)
// if (path[i][j] == 1)
// System.out.printf("第%d个商品放入到背包", i);
// System.out.println();
// }
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("第>> %d <<个商品放入到背包\n", i);
j -= w[i - 1];
}
i --;
}
}
}
结果:
0 0 0 0 0
0 1500 1500 1500 1500
0 1500 1500 1500 3000
0 1500 1500 2000 3500
================================
第>> 3 <<个商品放入到背包
第>> 1 <<个商品放入到背包
先看代码,如果看了一会儿还是不太懂就看下图:对代码的图解
文档信息
- 本文作者:Dkx
- 本文链接:https://pigpigletsgo.github.io/dou_note.github.io/2023/12/29/dongtaiguihua/
- 版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)