动态规划问题2——完全背包

178 阅读2分钟

完全背包问题描述

  • 有C种物品和一个容量为V的背包,每种物品都有无限件。第i件物品的体积是v[i],价值是w[i].求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。

与之前的01背包不同的是,每个物品都有无限个,对于同一个物品,可以放置多个。

基本解法

首先还是先来用基本解法来一遍,不带优化的。先看多个物品的状态转移方程。

  • 当不选择该物品时候,这个时候值跟01背包的一样,还是dp[i - 1][fv];
  • 当选择该物品时候,对于一个固定容量,如果该物品能被放进去,要想价值最大,那就能放几个放几个,这时候的最大个数是N = fv / w[i],即当前背包容量除以当前物品重量;得到最多放几个之后,就能计算出放入物品的总重量和最大价值了dp[i - 1][fv - N * w[i]] + N * v[i];
/**
     * @param V 最大容量
     * @param C 物品个数
     * @param v 价值数组
     * @param w 重量数组
     * @return
     */
    public int getMaxValue1(int V, int C, int[] v, int[] w) {
        int[][] dp = new int[C][V + 1]; //[物品][重量]  ->  最大价值
        //初始化起始数据
        for (int fv = 0; fv <= V; fv++) {
            //初始化只有一件物品时候,使劲往里面放吧,能放几件放几件
            if (w[0] <= fv) {
                int N = fv / w[0];
                dp[0][fv] = N * v[0];
            }
        }
        for (int i = 1; i < C; i++) {
            for (int fv = 0; fv <= V; fv++) {
                //假设放入N个当前物品
                //状态转移方程 : dp[i][fv]   =  max( dp[i-1][fv]  ,  dp[i-1][ fv- N*w[i] ] + N*v[i] )
                if (fv - w[i] >= 0) {
                    int N = fv / w[i];
                    //该物品大于剩余容量,可以放入背包,此时比较放入该物品与不放该物品的值
                    dp[i][fv] = Math.max(dp[i - 1][fv], dp[i - 1][fv - N * w[i]] + N * v[i]);
                } else {
                    //改物品不能放入背包,直接取不放该物品的值
                    dp[i][fv] = dp[i - 1][fv];
                }
            }
        }
        return dp[C - 1][V];
    }

    @Test
    public void testgetMaxValue1() {
        int V = 4;
        int C = 3;
        int[] v = {15, 20, 30};
        int[] w = {1, 3, 4};
        System.out.println(JSONObject.toJSONString(getMaxValue1(V, C, v, w))); //60
    }

空间优化

先来看之前01背包使用滚动数组优化后的代码:

public int getMaxValueUsingArray2(int V, int C, int[] v, int[] w) {
    int[] dp = new int[V + 1]; //[重量],表示此重量时候能获得的最大价值
    //初始化起始数据
    Arrays.fill(dp, 0);
    for (int i = 0; i < C; i++) { //物品
        for (int fv = V; fv >= w[i]; fv--) { //重量
            //从后往前遍历,物品不会被重复放置
            dp[fv] = Math.max(dp[fv], dp[fv - w[i]] + v[i]);
        }
    }
    return dp[V];
}

当时在内层遍历时候,对于01背包问题,因为物品都只有一个,所以必须从后往前遍历,但是对于我们的完全背包问题,我们从0往后遍历,则刚好符合物品可以放置多个。 下面是经过优化后的代码:

/**
    * @param V 最大容量
    * @param C 物品个数
    * @param v 价值数组
    * @param w 重量数组
    * @return
    */
   public int getMaxValue2(int V, int C, int[] v, int[] w) {
       int[] dp = new int[V + 1]; //[重量]  ->  最大价值
       for (int i = 0; i < C; i++) {
           for (int fv = 0; fv <= V; fv++) {
               if (fv - w[i] >= 0) {
                   dp[fv] = Math.max(dp[fv], dp[fv - w[i]] + v[i]);
               }
           }
       }
       return dp[V];
   }