动态规划(DP)
- 动态规划,(Dynamic Programming) : 通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法。动态规划常常适用于有重叠子问题和最优子结构性质的问题。
- 分治与动态规划
共同点:二者都要求原问题具有最优子结构性质,都是将原问题分而治之,分解成若干个规模较小(小到很容易解决的程序)的子问题.然后将子问题的解合并,形成原问题的解.
不同点:分治法将分解后的子问题看成相互独立的,通过用递归来做。
动态规划将分解后的子问题理解为相互间有联系,有重叠部分(下一个子阶段的求解是建立在上一个子阶段的解的基础上进一步求的解),需要记忆,通常用迭代来做。
3. 问题特征
最优子结构:当问题的最优解包含了其子问题的最优解时,称该问题具有最优子结构性质。
重叠子问题:在用递归算法自顶向下解问题时,每次产生的子问题并不总是新问题,有些子问题被反复计算多次。动态规划算法正是利用了这种子问题的重叠性质,对每一个子问题只解一次,而后将其解保存在一个表格中,在以后尽可能多地利用这些子问题的解。
动态规划之背包问题系列
背包问题主要是指一个给定容量的背包,若干具有一定价值和重量的物品,如何选择物品放入背包使物品的价值最大,其中又分为 01背包(所有物品不能重复)和 完全背包(完全背包指的是:每种物品都有无限件可使用)
这里 的问题属于01背包,即每个物品最多放一个,而无限背包可以转化成01背包、
01背包:
背包问题,有一个背包,容量为4磅,现有如下物品:
物品 重量 价格
吉他(G) 1 1500
音响(S) 4 3000
电脑(L) 3 2000
-
要求 讲物品装入背包,在不超出容量的情况下使背包的总价值最大
-
要求装入的物品不能重复
背包的填表的过程:
物品 | 0磅 | 1磅 | 2磅 | 3磅 | 4磅 |
---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | |
吉他(G) | 0 | 1500 | 1500 | 1500 | 1500 |
音响(S) | 0 | 1500 | 1500 | 1500 | 3000 |
电脑(L) | 0 | 1500 | 1500 | 2000 | 1500+2000=3500 |
分析:
- 若现在只有吉他,这时不管背包容量多大,只能放一个吉他,价值1500
- 若现在有吉他和音响,当背包是1,2,3磅时,就放吉他价值1500。当是4磅,就放价值更高的音响,价值3000
- 若现在有吉他,音响,电脑,当背包是1,2磅时,就放吉他价值1500。当是3磅,就放价值更高的音响,价值2000,当是4磅,若放价值更高的音响,价值3000,但若放吉他 和 电脑,价值3500
利用动态规划来解决。每次遍历到第i个物品,根据w[i](第i个物品的重量)和v[i](第i个物品的价格)来确定是否需要将该物品放入背包,即对于给定的 n个物品,设v[i], w[i]分别为第i个物品的价值和重量,C为背包的容量。再令v[i][j]表示在前i个物品中能够装入容量为j的背包的最大价值。则我们有下面的结果:
v[i][0] = v[0][j] = 0; // 将第一行第一列置为0
当w[i]>j时,v[i][j] = v[i-1][j]; // 当准备加入新增的商品容量 大于 当前背包容量时,就采用上一个单元格的装入策略
当w[i]<j=时,v[i][j] = max{v[i-1][j],v[i-1][j-w[i]]+v[i]} // 当准备加入的商品容量 小于等于 当前背包的容量,装入的策略变成求一个最大值(v[i-1][j]:就是上一个单元格的装入策略;v[i]:表示当前商品的价值;v[i-1][j-w[i]]:装入 i-1商品,到剩余空间j-w[i]的值)
验证:(将背包的填表看作是一个二维数组)
当w[i]>j时,v[i][j] = v[i-1][j];
当w[i]<=j时,v[i][j] = max{v[i-1][j],v[i-1][j-w[i]]+v[i]}
(1) v[1][1] =1
1. i=1 j=1
2. w[i]=w[1]=1 吉他的重量1磅 j = 1 背包容量1磅
3. w[i]<=j时,v[1][1] = max{v[0][1],v[0][1-1]+v[1]} ={0,1500} = 1500
(2) v[3][4]=3500
1. i=3 j=4
2. w[i]=w[3]=3 j=4
3. w[i]<j时 v[3][4] = max{v[3-1][4],v[3-1][4-w[3]]+v[3]}
=max{v[2][4],v[2][1]+v[3]}
= max{3000,1500+2000}=3500
代码实现:
package 动态规划;
public class Bag {
public static void main(String[] args) {
int[] w = {1, 4, 3}; // 物品的重量
int[] value = {1500, 3000, 2000}; // 物品的价值 这里的val[i],就是前面讲的v[i]
int m = 4; // 背包的容量
int n = value.length; // 物品的个数
// 创建二维数组 就是背包的填表 的存储结构 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中行的数目v.length
v[i][0] = 0; // 将第一列设置为0
}
for (int i = 0; i < v[0].length; i++){//二维数组v中列的数目 v[0].length
v[0][i] = 0; // 将第一行设置为0
}
// 根据前面得到公式来进行动态规划处理
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] 变成 value[i-1]
//v[i][j] = Math.max(v[i-1][j],(value[i - 1]+v[i-1][j - w[i - 1]]));
// 为了记录商品种类放进背包的情况,我们不能使用上面简单地公式进行处理,需要使用if-else
if (v[i-1][j] > (value[i - 1]+v[i-1][j - w[i - 1]])){
v[i][j] = v[i-1][j];
} else { // 把当前情况记录到path
v[i][j] = (value[i - 1]+v[i-1][j - w[i - 1]]);
path[i][j] = 1;
}
}
}
}
// 输出一下v, 查看情况
for (int i = 0; i < v.length; i++){
for (int j = 0; j < v[i].length ; j++){
System.out.print(v[i][j]+"\t");
}
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个商品放入背包\n",i);
// }
// }
// }
//这种算法会将所有的放入情况都得到,而我们只需要最后的那个价值总数最大的组合
//则path从后开始找
//path是用来记录,当增加一种可放入物品时,当前最大价值是否包含另外新加进来的物品价值,若包含,就在path中标记1 对应表中最后的单元格3500
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]; //此时j就是包的容量 此时放入w[i] 要调整j的大小 剩余容量 -=当前物品的容量
}
i--;
}
/*
0 0 0 0 0
0 1500 1500 1500 1500
0 1500 1500 1500 3000
0 1500 1500 2000 3500
------------------------------------
第3个商品放入背包
第1个商品放入背包
*/
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 25岁的心里话
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现