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

  • 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(动态规划)问题基本有四个主要步骤:

  1. 描述一个最优解的结构(最优子结构),借助“图”可利于分析问题;
  2. 分析重叠子结构性质(计算某一种情况时,会重复出现之前的计算步骤),从而确定“自底向上”or“带备忘录的自顶向下”解题方法;
  3. 关键:递归定义状态方程;
  4. 构建路径(此步依题而定,如果只求最优解则可以省略)。

    设:
    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)                        ji,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比较的是:待装的物品中,将哪个装入,可使得总价值最大(最优解)。

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

关于dp01背包问题的几点理解(二维数组,java实现)_第1张图片

接下来,直接贴代码:

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]);

你可能感兴趣的:(算法设计与分析)