动态规划-背包问题

普通背包问题

不废话,直接上代码。

/** 
 * 动态规划,背包问题。
 * 输入为:int n: 物品的种类数。 int[n] weight: 各件物品的重量。 int[n] value: 各种物品的价值。
 *        int W: 背包最大的装载重量。
 * 输出:V[n][b]的值, 最大的装载价值。 x[n] 各类物品的装载数量。
 * 
 * @author huangyongye
 *
 */


public class Bag {

    private int[][] V; // 备忘录,V[i][j] 表示只装前面的i种物品时,最大的装载价值
    private int[][] I; // 备忘录,I[i][j] 表示最后一个装载的物品的类别。

    /**
     * 
     * @param weight 各类物品的重量
     * @param value 各类物品的价值
     * @param b 最大的载重
     * @return
     */
    public int maxValue(int[] weight, int[] value, int b) {
        // 返回最大的装载价值
        int n = weight.length; // 物品的类别数量
        V = new int[n+1][b+1]; // 备忘录
        I = new int[n+1][b+1];
        /* 1.初始化 */
        for(int i = 0; i < n+1; i++)
            V[i][0] = 0;
        for(int j = 0; j < b+1; j++)
            V[0][j] = 0;

        /* 2.分割子问题,从小到大递推解决 */ 
        for(int i = 1; i < n+1; i++) {
            for (int j = 1; j < b+1; j++) {
                if(j < weight[i-1]) { // 如果能装的重量小于物品的单个重量
                    V[i][j] = V[i-1][j];
                    I[i][j] = I[i-1][j];
                }
                else {
                    // 递推方程:V[i][j] = (/*不装i*/V[i-1][j] > /*装i*/V[i][j-weight[i-1]] + value[i-1]) ? 
                    //  V[i-1][j] : V[i][j-weight[i-1]] + value[i-1];
                    if(V[i-1][j] > V[i][j-weight[i-1]] + value[i-1]) {
                        // 如果不装的话
                        V[i][j] = V[i-1][j];
                        I[i][j] = V[i-1][j];
                    }
                    else {
                        // 否则装
                        V[i][j] = V[i][j-weight[i-1]] + value[i-1];
                        I[i][j] = i-1;
                    }
                }
            }
        }   
        int[] result = labels(I, weight, b);
        /* 3.构造解,输出选择结果 */
        for(int i: result) {
            System.out.print(i + " ");
        }
        return V[n][b];
    }


    /**
     * 返回装入的物品数量,构造解
     * @param I
     * @return
     */
    public int[] labels(int[][] I, int[] weight, int b) {
        int n = weight.length; // 物品的数量
        // 初始化
        int[] result = new int[n];
        for(int i = 0; i < n; i++) {
            result[i] = 0;
        }
        int y = b; // 总重量
        while(I[n][y] != 0) {
            int j = I[n][y]; // 最后放入的物品
            while(I[n][y] == j) {
                y = y - weight[j];
                result[j]++;
            }
        }
        return result;
    }

    public static void main(String[] args) {
        int[] weight = {2,3,4,7};
        int[] value = {1,3,5,9};
        int b = 10;
        Bag bt = new Bag();
        int result = bt.maxValue(weight, value, b);
        System.out.println("\nmax value is "+ result);
    }
}

0-1背包问题

和普通背包问题相比,0-1背包问题多了一个限制, 就是某类物品只有一个,所以只能放置一遍。所以只需要对递推方程做简单修改就没问题了。具体说明见代码。

/**
 * 动态规划:0-1背包问题
 * 输入:int[n] weight 物品的重量 
 *      int[n] value 物品的价值 
 *      int b 物品最大的载重量
 * 输出:boolean[n] opt 选择或者不选择该物品。
 * 
 * 思路:和普通的背包问题相比,这里的每类物品只有一件。所以递推函数需要做相应的调整。
 * V[i][j] = max{装: V[i-1][j], 不装:V[i-1][j-weight[i]] +value[i]}
 * 所以背包问题和0-1背包问题只有一点点区别,就是递推方程的第二项的下标是i-1而不是i.
 * 
 * @author huangyongye
 *
 */
public class Bag01 {

    /**
     * @param weight 物品的重量
     * @param value 物品的价值
     * @param b 背包载重量
     * @return maxValue 最大装载价值
     */
    public static int maxV(int[] weight, int[] value, int b) {
        int maxValue = 0;
        int n = weight.length; // 物品类别数
        int[][] V = new int[n+1][b+1]; //V[i][j] 表示只装载前i种物品,且重量不超过j时最大装载价值。 
        int[][] I = new int[n+1][b+1]; //I[i][j] 表示只装载前i种物品,且重量不超过j时达到最大装载价值最后装入的物品。

        /* 1.初始化 */
        for(int i = 0; i <= n; i++) 
            V[i][0] = 0;
        for(int j = 0; j <= b; j++)
            V[0][j] = 0;

        /* 2.分割子问题,从小到大,两重循环遍历了所有的可能情况。 */
        for(int i = 1; i <= n; i++) {
            for(int j = 1; j <= b; j++) {
                if(j < weight[i-1]) { // 如果当前载重放不下第i(下标为i-1)个物品,则继续(增加重量)
                    I[i][j] = I[i-1][j];
                    continue;
                }
                else if(V[i-1][j] > V[i-1][j-weight[i-1]] + value[i-1]) { // 不装第i个物品
                    V[i][j] = V[i-1][j];
                    I[i][j] = I[i-1][j];
                }
                else { // 装第i个物品
                    V[i][j] = V[i-1][j-weight[i-1]] + value[i-1];
                    I[i][j] = i-1;
                }
            }
        }
        maxValue = V[n][b];
        // 记录物品是否被选中
        boolean[] opt = new boolean[n];
        opt = labels(I, weight, b);
        /* 输出选择结果 */
        for(boolean i: opt) {
            System.out.print(i + " ");
        }
        return maxValue;
    }

    /**
     * 记录物品选择的结果
     * @param i
     * @param weight
     * @param b
     * @return
     */
    private static boolean[] labels(int[][] I, int[] weight, int b) {
        /* 1.初始化*/
        int n = weight.length;
        boolean[] opt = new boolean[n];
        for(int i = 0; i < n; i++) {
            opt[i] = false;
        }
        int y = b;
        /* 2.回退找解 */
        while(I[n][y] != 0) {
            int j = I[n][y];
            opt[j] = true;
            y = y - weight[j];
        }

        return opt;
    }

    public static void main(String[] args) {
        int[] weight = {8,6,4,3};
        int[] value = {12,11,9,8};
        int b = 13;
        int maxValue = maxV(weight, value, b);
        System.out.println("\nthe maxValue is " + maxValue);
    }
}

复杂度分析

上面的两个问题都很容易分析。
第一部分:分割子问题中使用两重循环,内部运算都是常数时间复杂度,所以总的时间复杂度是O(n2)。
第二部分:构造解,每次回退至少一步求解一个装入的物品,所以复杂度为O(b),算上初始化的时间复杂度为O(n)。
所以主要时间复杂度为第一部分,即O(n2)。而空间复杂度,主要是保存了备忘录,二维数组V和I,复杂度为O(nb).

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值