背包问题

01背包问题

  • 0-1背包,最简单的背包问题
  • 包裹里能装N件物品,每一件价值Wi,耗费Ci,总耗空间不超过V,求W的最大值;
  • 状态转移方程:F[i, v] = max{F[i-1, v], F[i-1, v-Ci] + Wi};
  • 如何理解状态转移方程?F[i, v]表示面对前i件物品,容量为v的背包中可获得的最大价值;
private static void help() {
    int N = 4;
    int V = 11;
    int[][] F = new int[N+1][V+1]; //very clever and essential;
    int[] C = {2, 4, 5, 6};
    int[] W = {1, 2, 1, 2};
    pack01(F, C, W);
    for (int i=0; i<=N; i++)
        System.out.println(Arrays.toString(F[i]));
}

public static void pack01(int[][] F, int[] C, int[] W) {
    int N = F.length, V = F[0].length;
    for (int i=1; i<N; i++) {
        for (int v=0; v<C[i-1]; v++) {
            F[i][v] = F[i-1][v];
        }
        for (int v=C[i-1]; v<V; v++) {
            F[i][v] = Math.max(F[i-1][v], F[i-1][v-C[i-1]]+W[i-1]);
        }
    }
}

/* the results;
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1]
[0, 0, 1, 1, 2, 2, 3, 3, 3, 3, 3]
[0, 0, 1, 1, 2, 2, 3, 3, 3, 3, 3]
[0, 0, 1, 1, 2, 2, 3, 3, 3, 3, 4]
*/

得出的即为结果就是最终总共的N件物品里要存入不超过容量V的所有组合里价值最大的那一个!其中的时间复杂度为O(VN),空间复杂度是O(VN);能不能继续优化?当然是可以的!空间方面可以优化到O(V);下为优化后的代码:

  private static void help() {
    int N = 4;
    int V = 10;
    int[] C = {2, 4, 5, 6};
    int[] W = {1, 2, 1, 2};
    int[] F1 = new int[V+1];
    pack01_1(F1, C, W);
    System.out.println(Arrays.toString(F1));
}
public static void pack01_1(int[] F, int[] C, int[] W) {
    int N = C.length+1, V = F.length;
    for (int i=1; i<N; i++) {
        for (int v=V-1; v>= C[i-1]; v--) {
            F[v] = Math.max(F[v-C[i-1]]+W[i-1], F[v]);
        }
    }
}

空间优化后的01背包问题不方便回溯找寻最优解的解组成,因为一维数组动态覆盖掉了之前的所有物品的信息,只剩下最后一轮物品在装与不装时的结果。而空间未优化之前是可以很方便实现最优解回溯的,下面贴出二维数组F在F[N][V]具备的最优解时,回溯找所有物品是否存放的代码,使用item[]数组存放1~N物品是否最终放入的结果,1代表放入,0代表没放入;

public static int[] resolveItems(int[][] F, int[] C) {
    int[] items = new int[F.length-1]; //N件物品
    int N = F.length-1, V = F[0].length-1;
    for (int i=N; i>=1; i--) {
        if (F[i][V] != F[i-1][V]) {
            items[i-1] = 1;
            V -= C[i-1];
        }
    }
    return items;
}
 /*
 [0, 1, 0, 1]
 */

完全背包问题

  • 有N种物品,放一个容量为V的包,每一种有无限多个可用,
  • 放入第i种物品的空间费用是Ci,价值是Wi,问怎么放物品在不超过V的前提下,可以使得总价值最大?
  • 每种物品已经不再是简单的取或者不取了,而是取0件,1件,2件,一直到 V/Ci 件;
  • 完全背包的状态转移方程可以依照之前的01背包状态方程写出如下:
    F[i, v] = max{F[i-1, v-kCi] + kWi | 0 <= kCi <= v};
    但是可以通过转化为01背包,得到简化后的状态方程如下:
    F[i, v] = max{F[i-1, v],F[i, v-Ci ]+Wi };

代码实现如下:

private static void help() {
    int N = 4;
    int V = 11;
    int[] C = {2, 4, 5, 6};
    int[] W = {1, 2, 1, 2};
    int[] F1 = new int[V+1]; 
    completePack(F1, C, W);
        System.out.println(Arrays.toString(F1));
}
private static void completePack(int[] F, int[] C, int[] W) {
    int  N = C.length, V = F.length;
    for (int i=1; i<=N; i++) {
        for (int v=C[i-1]; v<V; v++) {
            F[v] = Math.max(F[v], F[v-C[i-1]] + W[i-1]);
        }
    }
}

/*results
[0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5]
*/
  • 思考为什么完全背包的代码和01背包的代码几乎一样,而仅仅是里面的 v 顺序不是从 V-1 到C[i-1]而是逆序遍历?
  • 因为01背包必须保证 F[i,v]在i时只能有F[i-1,v]和F[i-1,v-C[i-1]]推出,因为每个物品都不一样!但是完全背包里,F[i,v]是可以由F[i-1,v]甚至是F[i,v-C[i-1]]推出的,因为同一物品可以取多个!所以如果还是让v从V遍历到C[i-1],那么F[i,v-C[i-1]]是没有值的,所以算出的F[i,v]是不对的,只能是让v从小到大计算方可;

多重背包问题

  • 有N种物品,放一个容量为V的包,第 i 种有 Mi 个可用,
  • 放入第i种物品的空间费用是Ci,价值是Wi,问怎么放物品在不超过V的前提下,可以使得总价值最大?
  • 每种物品已经不再是简单的取或者不取了,而是取0件,1件,2件,一直到 Mi 件;
  • 完全背包的状态转移方程可以依照之前的01背包状态方程写出如下:
    F[i, v] = max{F[i-1, v-kCi] + kWi | 0 <= kCi <= v, 0 <= k <= Mi };
    但是可以通过转化为01背包,得到简化后的状态方程如下:
    F[i, v] = max{F[i-1, v],F[i, v-Ci ]+Wi };

代码如下:

private static void help() {
    int N = 4;
    int V = 10;
    int[] C = {2, 4, 5, 6};
    int[] W = {1, 2, 1, 2};
    int[] F1 = new int[V+1]; 
    int[] M = {0, 0, 1, 1};
    multiplePack(F1, C, W, M);
    System.out.println(Arrays.toString(F1));
}

   private static void multiplePack(int[] F, int[] C, int[] W, int[] M) {
    int N = C.length, max = 10001, V = F.length-1;
    int[] value = new int[max], size = new int[max];
    int n1 = 0;
    for (int i=1; i<=N; i++) {
        int k = 1;
        while (M[i-1] >= k) {
            value[++n1] = W[i-1] * k;
            size[n1] = C[i-1] * k;
            M[i-1] -= k;
            k <<= 1;
        }
        value[++n1] = W[i-1] * M[i-1];
        size[n1] = C[i-1] * M[i-1];
    }

    for(int i=1; i<=n1; i++)
           for(int j=V; j>=size[i]; j--)
              F[j]=Math.max(F[j],F[j-size[i]]+value[i]); 
}

多重背包算是背包问题里的中等难度的了,但是对于我来说已经很难了!看了一晚上才搞明白那个二进制分解的原理,上面的代码也就是基于此得出来的答案。附上我看的以一篇非常靠谱的博客-经典背包问题 01背包+完全背包+多重背包,写的非常的好!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值