动态规划-01背包

01背包

可解决问题

  • 数组的组合问题

    给定一个数组nums,每个元素只能取一次,求从数组中取任意几个元素的和,等于target的所有组合数

    背包大小为:target

    递推方程:dp[j] += dp[j - nums[i]]

    例题:目标和(难点在于转化为01背包问题)

 

问题描述

有n件物品和一个最多能容纳重量为bagSize物品的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大。

例题解析

  重量 价值
物品0 1 15
物品1 3 20
物品2 4 30 

背包容量为:4

二维dp数组


  • 确定dp数组及其下表的含义

    dp[i][j]: 表示从下标为[0-i]的物品里任意取,放进容量为j的背包,价值总和最大是多少

    下标i:表示从[0-i]的物品里任意取物品(物品i可取可不取)

    下标j:表示背包的容量大小


  • 确定递推公式

    对于物品i,有两种选择状态:不取

    • 取物品i:

        如果我们取了物品i,那么当前下标的价值(不是最大价值)dp[i-1][j-weight[i]]决定。

        (背包容量减去物品i的重量,表示取物品i)

        dp[i-1][j-weight[i]]表示:放入物品i后,减去物品i的重量,在剩余容量中选取[0,i-1]物品的最大价值。

        即:取物品i后的价值为  dp[i-1][j-weight[i]]+values[i]   (不是最大价值)

    • 不取物品i:

        如果我们不取物品i,那么当前下标的价值(不是最大价值)由[0,i-1]任取物品的最大价值决定。

        即:不取物品i的价值为 dp[i-1][j] (不是最大价值)

     因此,在背包容量j中取[0,i]内任取物品的最大价值为:

     dp[i][j]=max(dp[i-1][j-weight[i]]+values[i],dp[i-1][j])


  • 初始化dp数据

    由递推公式得知,dp[i][j]由dp[i-1][j]或dp[i-1][j-weight[i]]的到,因此,我们需要初始化当i=0是的最大价值.

    初始化代码如下:

for(int j=weight[0];j<=bagSize;j++){
    dp[0][j]=values[0];
}

  • 确定遍历顺序

    在本题中,先遍历背包,再遍历物品或者先遍历物品,在遍历背包其实都是可以的。

    但为了便于理解,在题目没有特殊要求的情况下,选择先遍历物品再遍历背包。

    遍历的代码如下:

//本题的weight[i]表示第i个物品的重量,相当于物品
for(int i=1;i<weight.length;i++){
    for(int j=0;j<=bagSize;j++){
        dp[i][j]=dp[i-1][j];//先将[0,i-1]最大价值复制过来,再讨论取不取物品i
        if(j>=weight[i]){
            dp[i][j]=Math.max(dp[i-1][j],dp[i-1][j-weight[i]]+values[i]);
        }
    }
}

完整测试代码:

完整java代码(点击展开)
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 + 1][bagsize + 1];
    //初始化:背包容量为0时,能获得的价值都为0
    for (int i = 0; i <= wlen; i++){
       dp[i][0] = value0;
    }
    //遍历顺序:先遍历物品,再遍历背包容量
    for (int i = 1; i <= wlen; i++){
        for (int j = 1; j <= bagsize; j++){  
             dp[i][j] = dp[i - 1][j];
             if (j >= weight[i - 1]){
                dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - weight[i - 1]] + value[i - 1]);
             }
        }
    }
    //打印dp数组
    for (int i = 0; i <= wlen; i++){
        for (int j = 0; j <= bagsize; j++){
            System.out.print(dp[i][j] + " ");
        }
        System.out.print("\n");
    }
}

 

一维dp数组(先物品,后背包,不可颠倒)


  在二维数组中,我们发现dp[i][j]=max(dp[i-1][j],dp[i-1][j-weight[i]]+weight[i])中,如果我们先把dp[i-1][j]的值赋值到dp[i][j]上,那么dp[i][j]=max(dp[i][j],dp[i][j-weight[i]]+weight[i]),我们发现,可以把二维dp[i][j]转为一维dp[j]。


  • 确定dp数组及其下标的含义

    dp[j]:表示容量为j的背包的最大价值

    下标j:表示背包的容量


  • 确定递推公式

    对于物品i,有不取两种状态:

    • 取物品i:

        如果取物品i,则价值为dp[j-weight[i]]+values[i]

        dp[j-weight[i]]:表示扣除物品i的重量后,剩余容量的最大价值.

    • 不取物品i:

        如果不选取物品i,则价值为 dp[j]

        dp[j]:表示不容量j的价值,不选取i,不扣除i的总量

    因此,对于容量为j的背包,最大价值为 dp[j]=max(dp[j],dp[j-weight[i]]+values[i])


  • 初始化数据

    根据递推公式,无论如何,dp[0]都会自己进行一次0和dp[0]+values[i]作比较,取最大值,则dp[j]数组默认初始化为零即可.


  • 确定遍历顺序

    一维dp数组的物品遍历顺序和二维相同,但是背包遍历应该由大到小遍历。

    如果从小到大,表示物品可以无限选取属于完全背包。

    01背包一维数组遍历代码如下:

for(int i=0;i<weight.length;i++){
    for(int j=bigSize;j>=weight[i];j--){
        dp[j]=Math.max(dp[j],dp[j-weight[i]]+values[i]);
    }
}

 

posted @ 2022-11-04 09:54  是小张呀qaq  阅读(53)  评论(0)    收藏  举报