背包算法

257 阅读4分钟

1.0-1背包算法

1.1.概述

0-1背包算法:有n个物品,每种物品只有一个,每个物品有自己的价值和重量,有一个容量为m的背包,求这个背包最多能装价值为多少的物品

WeightValue
物品0115
物品1320
物品2430
背包的最大容量m = 4

1.2.暴力解法

每个物品只有两个状态:取与不取,可以进行暴力搜索,枚举所有的情况

  • 物品0 + 物品1:m = 4,value = 35
  • 物品0 + 物品2:m = 5,舍弃
  • 物品1 + 物品2:m = 7,舍弃

暴力算法的结果为:物品0 + 物品1,最大价值为35;时间复杂度:O(n^2)

1.3.dp数组

1.3.1.含义

dp[i][j]:表示[0,i]之间的物品任取放入容量为j的背包里

1.3.2.递推公式

在以下两种情况下有最大值:

  • 不放物品i:dp[i - 1][j],此时物品i的重量大于背包容量,物品i无法放入背包,所以最大价值和前一个相等
  • 放入物品i:dp[i - 1][j - Weight[i]] + Value[i] ,dp[i - 1][j - Weight[i]]就是放入物品i之前背包的最大价值,那加上物品i的价值Value[i]就等得到放入物品i的最大价值

递推公式:dp[i][j] = max(dp[i - 1][j],dp[i - 1][j - Weight[i]] + Value[i])

1.3.3.初始化

dp[i][j]背包容量:01234
物品0015151515
物品1015152035
物品2015152035
  • 首先初始化背包容量为0的一列:全部为0,

  • 再初始化物品0的一行:容量大于等于物品0重量的字段都可以填入价值15

  • 后续每个值根据左上方和正上方的值用递推公式可以得出:

    • 当前背包容量小于新物品重量时直接使用上一个值
    • 当前背包容量大于新物品重量时取旧值和新物品价值的最大值

1.3.4.完整代码



public static void main(String[] args) {

    int[] weight = {1, 3, 4};

    int[] value = {15, 20, 30};

    int bagsize = 4;

    testweightbagproblem(weight, value, bagsize);

}



public static void testweightbagproblem(int[] weight, int[] value, int bagsize) {

    int wlen = weight.length, value0 = 0;

    //定义dp数组:dp[i][j]表示背包容量为j时,前i个物品能获得的最大价值

    int[][] dp = new int[wlen][bagsize + 1];

    //初始化:背包容量为0时,能获得的价值都为0

    for (int i = 0; i < wlen; i++) {

        dp[i][0] = value0;

    }

    //初始化只装物品0的情况

    for (int i = 0; i < bagsize + 1; i++) {

        if (i < weight[0]) {

            dp[0][i] = 0;

        } else {

            dp[0][i] = value[0];

        }

    }

    //遍历顺序:先遍历物品,再遍历背包容量

    for (int i = 1; i < wlen; i++) {

        for (int j = 1; j <= bagsize; j++) {

            if (j <  weight[i]) {

                dp[i][j] = dp[i - 1][j];

            } else {

                dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);

            }

            //打印dp数组

            for (int k = 0; k < wlen; k++) {

                for (int l = 0; l <= bagsize; l++) {

                    System.out.print(dp[k][l] + " ");

                }

                System.out.print("\n");

            }

            System.out.println("\n");

        }

    }

}

1.4.一维dp数组

0-1背包问题还有一种更更加节省空间的解决方法,采用一维数组,就是在不加入新物品时就把dp[i - 1]那一层拷贝到dp[i]里,在加入新物品时就用新的价值,由此就可以只用dp[j]

  • 一维dp数组dp[j]的定义:

容量为j的背包,所背物品价值的最大值为dp[j]

  • 递推公式:

dp[j]可以由dp[j - Weight[i]]推导出来,dp[j]有两个选择,一是取自己dp[j](此时的自己就是代表上一轮赋值的最大价值,相当于dp[i - 1][j]),相当于不放新物品;另一个是取dp[j - Weight[i]] + Value[i],即放入新物品

所以递推公式:dp[j] = max(dp[j],dp[j - Weight[i]] + Value[i])

  • 初始化:

根据dp[j]的定义知道dp[0]初始化为0,因为dp数组在推导的时候是取最大价值,所以其他下标初始化为0就可以了,让dp数组初始化时能取最大价值,不被初始值覆盖

  • 遍历顺序:
for(int i = 0; i < weight.size(); i++) { // 遍历物品

    for(int j = bagWeight; j >= weight[i]; j--) { // 遍历背包容量

        dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);

    }

}

注意在遍历背包容量时是倒序遍历的,因为在求一个项的最大容量时,需要借助它前面的最大价值,如果不倒序遍历,前面的最大价值就先会被覆盖,无法求出下一项。

遍历流程:

  • 初始化

dp[j]:{0,0,0,0,0}

  • 遍历

    • i = 0,Weight[i] = 1,Value[i] = 15
015151515
  • i = 1,Weight[i] = 3,Value[i] = 20
015152035
  • i = 2,Weight[i] = 4,Value[i] = 30
015152035
  • 完整代码
    public static void main(String[] args) {

        int[] weight = {1, 3, 4};

        int[] value = {15, 20, 30};

        int bagWight = 4;

        testWeightBagProblem(weight, value, bagWight);

    }



    public static void testWeightBagProblem(int[] weight, int[] value, int bagWeight){

        int wLen = weight.length;

        //定义dp数组:dp[j]表示背包容量为j时,能获得的最大价值

        int[] dp = new int[bagWeight + 1];

        //遍历顺序:先遍历物品,再遍历背包容量

        for (int i = 0; i < wLen; i++){

            for (int j = bagWeight; j >= weight[i] ; j--){

                dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]);

            }

        }

        //打印dp数组

        for (int j = 0; j <= bagWeight; j++){

            System.out.print(dp[j] + " ");

        }

    }

2.完全背包

2.1.概述

在0-1背包的基础上,只要背包装得下,每件物品可以选择任意多件,从物品的角度来说,与之相关的策略不只有选或不选了,而是有取0件、取1件、取2件...直到取M/Weight

2.2.dp数组

当背包当前容量M小于新物品的重量时:直接使用上一项的最大价值dp[i][j] = dp[i - 1][j]

若能放下新物品,判断添加多少个新物品可以达到最大价值:dp[i][j] = max(dp[i][j],dp[i - 1][j - k * Weight[i - 1]] + k * Value[i - 1])

2.3.完整代码

为了方便演示,将条件变为:

WeightValue
物品0115
物品1235
物品2450
背包容量:4


class Knapsack {

    private static int[] Value = {15, 35, 50};

    private static int[] Weight = {1, 2, 4};

    private static int T = 4;



    private int[][] dp = new int[Value.length + 1][T + 1];



    public void solve() {

        for (int i = 1; i <= Value.length; i++) {

            for (int j = 1; j <= T; j++) {

                //在下面这个循环中,会在当前背包容量允许的条件下增加新物品的个数,最终该位置是最大价值

                for (int k = 0; k * Weight[i - 1] <= j; k++) {

                    dp[i][j] = Math.max(dp[i][j], dp[i - 1][j - k * Weight[i - 1]] + k * Value[i - 1]);

                }

            }

        }

        System.out.println("最大价值为:" + dp[Value.length][T]);

    }

}

下面是每一次的添加逻辑: