提个问题
背包问题与钢条切割 ,实际上是01背包和完全背包问题
- 01背包问题的最优子结构是:放还是不放,dp[i][j] ==>二维数组,记录了不同容量放不同物品价格的数据 。
- 钢条切割问题最优子结构:切还是不切,f[j] ==>一维数组,j代表长度相当于容量,只记录了不同容量的最优解数据。
更新2023/11/10
有没有发现一点不一样,钢条切割方法跟平常的一维数组背包问题解法不一样,就是双重循环,钢条切割是容量在外循环.
使用一维数组解决背包问题,01和完全。
问题1:为什么01背包问题,容量必须在内循环。
- 一般来说01背包问题容量如果在外循环,所导致的问题就是在背包每一个容量都会尝试放入所有的物品。但关键是动态规划解决背包问题是利用重复子问题的解解决问题,在你容量3的时候已经放入物品2,然后在容量4的时候你利用容量3的解的基础上再放入物品2这不就重复放入物品2了吗。所以理解了这个我们还可以解决一个问题—》问题2。
问题2: 完全背包问题容量和物品的内外循环顺序可以调换吗
- 可以调换,详情看问题1
问题3: 01背包问题容量内循环为什么从大到小,而完全背包问题从小到大。
- 这个问题非常关键,是解决完01背包问题 然后 如何解决完全背包问题。01背包问题使用一维数组时,容量内循环需要 for( int j=bagSize;;j–),容量由大到小,这是为了防止重复放入问题不能j++ 。可以看代码随想录。 理解了这个我们清楚,如果不是容量从大到小那么,会出现这个问题,当物品2的时候,解决容量5问题,递推公式是max{dp[5],dp[5-weight[2] ]+value[i2] }, 这个时候递推公式需要容量3,如果我们从小到大遍历容量,那么此时容量3已经添加了物品2,递推公式中dp[5-weight[2] ]+value[i2] 就错误了,同时如果每次容量从小到大遍历,容量3添加了物品2,容量5也添加了物品2,这个不就是重复添加物品吗,物品只有一个在01背包问题。
- 但是请想想,完全背包问题与01背包问题不同点在于每一个物品有数量无限,01背包每个物品只有一个。重复添加不就是正好解决了物品有无限个吗。误打误撞是吧。
期中考试题钢条切割,一眼背包问题结果写错了
钢条切割问题:
给定一段长度为n 英寸的钢条和一个价格表Qj,长度表为Lj,价格表和长度表相互关联。求切割钢条方案,使得销售收益F(n)最大。
网上的图,长度表L为长度i,价格表Q为价格pi。
int[] L={0,1,2,3,4,5,6,7,8,9,10};
int[] Q={0,1,5,8,9,10,17,17,20,24,30};
利用动态规划,自底向上解决这个问题。
切割长度问题:
- 切割:n的长度,分成不同长度的价值,最大长度.Lj为长度,Qj为价格
* 最优子结构:切与不切
* f(n)= max{f(n-Lj)+Q(Lj),f(n) }
- 问题1:错误的最优子结构==》f(n)= max{ f(n-Lj)+f(Lj) , f(n) }
解析: 最优子结构:在切的这部分==》正确是f(n-Lj)+Q(Lj),切了之后,被切的这部分的长度还可以再细分成不同长度所以用了f(Lj)没用Q(Lj),然后答案就错了。这部分是最优子结构思想出了问题,这里表示的是我每一次切的长度,什么意思,我们看一下 f(n-Lj)+f(Lj) 这部分中Lj表示的是集合(从j=0…),表达的意思是长度切多少不确定,那就遍历取最大,所以必须用Q(Lj)表示每一次切的长度的价格。所以最优子结构其实可以这样 max{max{ f(n-Lj)+Q(Lj) } ,f(n) }
代码
public static void main(String[] args){
int[] L={0,1,2,3,4,5,6,7,8,9};
int[] Q={0,1,5,8,9,10,17,17,20,24};
int n=8;
int[] f = cut(n, Q, L);
for(int i=L[1];i<=n;i++)
System.out.println("长度为"+i+"价值:"+f[i]);
}
/**
* 切割长度问题:
* 切割:n的长度,分成不同长度的价值,最大长度.Lj为长度,Qj为价格
* f(n)= max{f(n-Lj)+Q(Lj),f(n-Lj) }
*
*问题:错误的最优子结构==》f(n)= max{f(n-Lj)+f(Lj),f(n-Lj) }
* @param n
* @param Q
* @param L
* @return
*/
static int[] rod_cutting(int n,int[] Q,int[] L){
int[] f=new int[n+1];
for(int i=L[0];i<=n;i++){//容量
for(int j=1;j<L.length;j++){//钢条长度
if(L[j]<=i)
f[i]=Math.max(f[i], f[i-L[j]]+Q[L[j]]);
}
}
return f;
}
01背包问题
代码
public static void main(String[] args){
int[] weight={2,1,3,2};
int[] value={12,10,20,15};
int bagsize=5;
// int[] weight = {1,3,4};
// int[] value = {15,20,30};
// int bagsize = 4;
// int bagsize = 100;
//
// int[] weight = {18,42,88,3};
// int[] value ={141,136,192,223};
testWeightBagProblem(weight, value, bagsize);
}
/**
* 动态规划获得结果
* @param weight 物品的重量
* @param value 物品的价值
* @param bagSize 背包的容量
*/
public static void testWeightBagProblem(int[] weight, int[] value, int bagSize) {
// 递推公式:max{dp[i-1][j],dp[i-1][j-weight[i]]+value[i]}
// 动态规划:自下而上==> 需要初始化 对于背包问题的初始化,
// 根据递推公式,dp[i][j]由 max{dp[i-1][j],dp[i-1][j-weight[i]]+value[i]}得出,
// 也就是之前的dp[i-1][j]或者[i-1]dp[i-1][j-weight[i]]+value[i] 得到,所以dp[i][j]需要初始化dp[i-1][ j从0到最大值 ]
// 一直往前推直到i=0时不能往前推了,初始化i=0那一行
int[][] dp=new int[weight.length][bagSize+1];
for(int j=weight[0];j<=bagSize;j++){
dp[0][j]=value[0];
}
for(int i=1;i<weight.length;i++){
for(int j=0;j<=bagSize;j++){
if(j<weight[i]) dp[i][j]=dp[i-1][j];
else dp[i][j]=Math.max(dp[i-1][j],dp[i-1][j-weight[i]]+value[i]);
}
}
for(int i=0;i<weight.length;i++){
for (int j=0;j<=bagSize;j++)
System.out.print(dp[i][j]+"\t");
System.out.println();
}
System.out.println("背包问题:最大的价值的是=》"+dp[weight.length-1][bagSize]);
}
//一维数组版本
public static void testWeightBagProblem(int[] weight, int[] value, int bagWeight){
int wLen = weight.length;
//定义dp数组:dp[j]表示背包容量为j时,能获得的最大价值
int[] dp = new int[bagWeight + 1];
//遍历顺序:先遍历物品,再遍历背包容量
for (int i = 0; i < wLen; i++){
for (int j = bagWeight; j >= weight[i]; j--){
dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]);
}
}
//打印dp数组
for (int j = 0; j <= bagWeight; j++){
System.out.print(dp[j] + " ");
}
}