关于dp01背包问题的几点理解(二维数组,java实现)

    xiaoxiao2021-03-25  75

    01背包问题:给定N种物品和一个背包。物品i的重量是weight[i],其价值value[i] (i<=N),背包的容量为M。问应该如何选择装入背包的物品,使得装入背包的物品的总价值为最大?在选择物品的时候,对每种物品i只有两种选择,即装入背包或不装入背包。不能将物品i装入多次,也不能只装入物品的一部分。因此,该问题被称为0-1背包问题。例如:假设现有容量15kg的背包,另外有4件物品,分别为a1,a2,a3, a4。物品a1重量为3kg,价值为4;物品a2重量为4kg,价值为5;物品a3重量为5kg,价值为6 ;物品a4重量为6kg,价值为7 。问:将哪些物品放入背包可使得背包中的总价值最大?

    解决dp(动态规划)问题基本有四个主要步骤:

    描述一个最优解的结构(最优子结构),借助“图”可利于分析问题;分析重叠子结构性质(计算某一种情况时,会重复出现之前的计算步骤),从而确定“自底向上”or“带备忘录的自顶向下”解题方法;关键:递归定义状态方程;

    构建路径(此步依题而定,如果只求最优解则可以省略)。

    设: weight[i]:第i个物品的重量; value[i]:第i个物品的价值; f[i][j]重点内容:在只有i个物品,背包容量为j的情况下,问题的最大价值。 或者理解为:前i个物品装入容量为j的背包里获得的最大价值。 注意: f[i][0]:0容量时,所有物品都不取,最大价值为0; f[0][j]:没有物品可取,最大价值为0。

    因此,容易得出本题的状态方程: (1) f(i,0)=f(0,j)=0 (2) f(i,j)=f(i-1,j) j<wi f(i,j)=max{f(i-1,j) ,f(i-1,j-wi)+vi) j>wi

    (2)式表明:如果第i个物品的重量大于背包的容量,则物品i不能装入背包。此时,装入前i个物品得到的最大价值(最优解)和装入前i-1个物品得到的最大价值是相同的;

    反之,如果第i个物品的重量小于背包的容量,则会有两种情况: (a)如果把第i个物品装入背包,则背包物品的总价值等于第i-1个物品装入容量为j-weight[i] 的背包中的价值加上第i个物品的价值value[i]; (b)如果第i个物品没有装入背包,则背包中物品总价值就等于把前i-1个物品装入容量为j的背包中所取得的价值。 显然,取二者中价值最大的作为 前i个物品装入容量为j的背包中的最优解。

    注意!!!对于max函数的理解: 此时待选的物品仍有很多个,所以这是是一个比较最大值的过程,需要计算多次。不要单纯的把物品想象成只有一个,装进背包,价值一定增大。max比较的是:待装的物品中,将哪个装入,可使得总价值最大(最优解)。

    结合以下状态表格更容易理解。 横坐标为背包剩余容量(递增),纵坐标为选中物品的数量(递增)。 每一个单元格含义:在满足当前容量及物品个数要求时,所能创造的最大价值。

    接下来,直接贴代码:

    package dp0_1背包; public class Main { static final int M = 15;//背包容量 public static void main(String[] args) { int N = 4;//物品种类 int[] weight = {0, 3, 4, 5, 6};//为了题解方便,i对应第i个物品 int[] value = {0, 4, 5, 6, 7}; KnapSack(N, weight, value); } private static void KnapSack(int N, int[] weight, int[] value){ int[] x = new int[N+1]; int[][] f = new int[N+1][M+1]; for(int i=1; i<=N; i++) for(int j=1; j<=M; j++){ if(j < weight[i]) f[i][j] = f[i-1][j]; else f[i][j] = Math.max(f[i-1][j], f[i-1][j-weight[i]]+value[i]); //dp可以存储之前计算过的量,从而减少内存就体现在这里。如果收益比上一次大就记录下来,否则不记录,节省内存。 } //记录路径 //!!!在选择物品是否装入背包时,一定要逆序!! //因为判断下一个是否要装入时,是在减掉前面装入的所有容量之和之后,才判断下一个能否装入 //而不是一项一项累加 判断和是否超出容量界限。 int j = M; for(int i=N; i>=1; i--) if(f[i][j]>f[i-1][j]){ x[i] = 1; //第i个物品要选 j = j - weight[i]; } System.out.println("选中的物品是:"); for(int i=1; i<=N; i++) System.out.print(x[i] + " "); System.out.println(); } }

    结果:

    选中的物品是: 1 1 1 0 最后,可以看出:在计算f[i][j]时只使用了f[i-1][0……j],没有使用其他子问题,因此在存储子问题的解时,只存储f[i-1]子问题的解即可。这样可以用两个一维数组解决(降维),一个存储子问题,一个存储正在解决的子问题。这里只写出状态方程,代码交给诸位博友自由发挥吧。f[j]=max(f[j],f[j-weight[i]]+value[i]);
    转载请注明原文地址: https://ju.6miu.com/read-25411.html

    最新回复(0)