⭐️动态规划-0/1背包
什么是0/1背包问题,0/1背包问题是背包问题中一道重要的题目,大致意思是有n个物品,每个物品有重量w和价值v,要把这些物品中的一些物品放入容量为m的背包中,要求背包能放下而且其价值最大,0/1背包就是每个物品只有一个,只能是放或者不放。
❓问题描述
例题:给定一个背包,其容量为7,有4个物品,物品的重量和价值分别为{2,3,3,4},{5,10,15,20},每个物品最多只能放一次,要求放入背包中的物品的总价值最大,求出总价值。
🍉分析
对于动态规划的问题,还是先对问题进行划分
- 划分问题
最终结果是求容量为7的背包所能放的最大价值物品,那容量为7的背包所能放的最大价值物品依赖于容量为小于7的背包所能放的最大价值物品,以此类推,从最小的问题入手,可以逐渐求出最终结果。
- 确定动态规划方程
通过例题分析,对于背包容量和物品之间的存在两种关系,当前背包容量放不下当前物品,当前背包容量可以放下当前物品,由于有背包容量和物品这两项,所以一般采用二维的dp数组,需要明确的是dp[i] [j]的含义是“从下标0~i的物品中任意取,放入容量为j的背包中”,
- 背包容量放不下当前物品
dp[i][j]=dp[i][j−1],0<=i<n,w[i]>j dp[i][j] = dp[i][j-1], 0<=i<n,w[i]>j dp[i][j]=dp[i][j−1],0<=i<n,w[i]>j
- 背包容量可以放下当前物品
dp[i][j]=Max(dp[i−1][j],dp[i][j−w[i]]+v[i]),0<=i<n,w[i]<j dp[i][j] = Max(dp[i-1][j],dp[i][j-w[i]]+v[i]),0<=i<n,w[i]<j dp[i][j]=Max(dp[i−1][j],dp[i][j−w[i]]+v[i]),0<=i<n,w[i]<j
第一个动态规划方程的含义是背包放不下当前物品,那当前背包中的最大价值还是前i-1个物品
第二个动态规划方程的含义是背包可以放下当前物品,那就对放入物品i后的背包价值和不放物品i时的背包价值进行比较,取较大者,满足最优决策。
- 初始条件
对dp数组进行初始化,由于第i个物品依赖于第i-1个物品和j-1时的背包容量,所以dp数组的第一行和第一列是需要手动初始化的。第一行的含义是,第一个物品放入容量为0~j的背包中的价值(那也就是当背包容量小于物品重量的时候初始化为0,大于等于物品重量的时候初始化为物品价值),第一列含义是,背包容量为0时背包所能放的最大价值(背包容量为0,那么自然价值也为0),其他位置的值初始化随意,不过一般初始化为0。
dp数组
物品重量和价值
编号 | 重量weight | 价值value |
---|---|---|
0 | 2 | 5 |
1 | 3 | 10 |
2 | 3 | 15 |
3 | 4 | 20 |
💻代码
package com.dp;
import java.util.Arrays;
import java.util.Scanner;
/**
* @Author Lunau
* @Create 2022-03-31 15:38
* @Description 2 6 2 3 6 5 5 4 4 6
* @Result
*/
public class KnapSack {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入背包容量:");
int bagWeight = sc.nextInt();
System.out.println("请输入物品数量:");
int nums = sc.nextInt();
System.out.println("请输入物品重量和物品价值(重量和价值之间空格隔开," +
"每个物品之间空格或回车隔开):");
int[] weight = new int[nums];
int[] value = new int[nums];
for(int i=0;i<nums;i++) {
weight[i] = sc.nextInt();
value[i] = sc.nextInt();
}
System.out.println(getMaxValue(weight,value,bagWeight));
}
// 0/1背包
private static int getMaxValue(int[] weight,int[] value,int bagweight){
//定义dp数组 表示从0~i的物品中任意取,放进价值为j的背包中的最大价值
int[][] dp = new int[weight.length][bagweight+1];
//dp数组初始化 第一行初始化为第一个物品放进背包的价值
for(int j=weight[0];j<=bagweight;j++) {
dp[0][j] = value[0];
}
//遍历物品 第一个物品在dp数组初始化的时候已经放入计算过了
for(int i=1;i<weight.length;i++) {
//遍历背包容量 背包容量为0的时候已经被计算了
for(int j=1;j<=bagweight;j++) {
if (j<weight[i]) { //当前背包容量放不下物品i
dp[i][j] = dp[i-1][j];
} else { //当前背包容量可以放下物品i,判断放下最优还是原来的更优
dp[i][j] = Math.max(dp[i-1][j],dp[i-1][j-weight[i]]+value[i]);
}
}
}
//回溯求装入背包的物品
int[] goods = new int[value.length];
for(int j=bagweight,i=goods.length-1;i>0;i--) {
if(dp[i][j]>dp[i-1][j]) {
goods[i] = 1;
j = j-weight[i];
} else {
goods[i] = 0;
}
//对第一个物品是否放入背包进行特判
if(i-1==0&&dp[i-1][j]!=dp[i][j]) {
goods[0]=1;
}
}
//打印装入背包的物品
System.out.println(Arrays.toString(goods));
//返回dp数组右下角的值,最优结果
return dp[weight.length-1][bagweight];
}
}