01背包详解

内容:

1、01背包问题
2、01背包问题的优化
3、01背包问题不同条件下的初始化

01背包问题:

问题描述:给定一组物品,每种物品都有自己的重量和价格,在限定的总重量内,我们如何选择,才能使得物品的总价格最高。

问题转换:已知有一个最多可以装质量W的背包,还有N件物品,第i件物品的质量是w[ i ],价值是v[ i ],每种物品尽可以放一件,可以选者放或者不放,而且不可以超过背包的最大承受的重量

顾名思义,01背包,就是在每一件物品中抉择,是放还是不放,那么咱们先来设一个数组dp[ i ][ j ](意思是:前 i 件物品放到一个容量为V的背包中可以获得的最大价值,这前i个物品有的可能放了,有的没有放),
举个栗子:
W = 5   N = 4
w[ 1 ] = 2   w[ 2 ] = 1   w[ 3 ] = 3   w[ 4 ] = 2
v [ 1 ] = 3   v [ 2 ] = 2   v [ 3 ] = 4   v [ 1 ] = 2

那么咱们来看 如果说 第一件物品不放的话:dp[ 1 ][ 5 ] == 0 如果放入 : dp[ 1 ][ 5 - 2 ] == dp[ 1 ][ 3 ] == 3
假设说咱们第一件物品拿了,那么第二件物品如果不拿,相应的背包的重量也不会减少,就有:dp[ 2 ][ 3 ] == d[ 1 ][ 3 ] == 3
假设说咱们第一件物品拿了,那么第二件物品如果    拿,相应的背包的重量       会减少,就有:dp[ 2 ][ 3 - 1] == d[ 1 ][ 3 - 1 ] + v[ 2 ] == 5
所以可以建立递推关系式:
①  :dp[ i ][ j ] = dp[ i - 1 ][ j ]     第 i 件物品没有拿,所以背包可以承受的重量依然为 j ,而且价值也不会增多
②  :dp[ i ][ j ] = dp[ i - 1][ j - w[ i ] ] + v[ i ]  表示第 i 件物品拿了,所以背包可以承受的重量减去第 i 件物品的重量 ,而且价值增多 v[ i ]
由此可得递推关系为:dp[ i ][ j ] = max( dp[ i - 1 ][ j ] ,  dp[ i - 1][ j - w[ i ] ] + v[ i ] )  保证 j >= w[ i ]

代码如下:
 

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
using namespace std;
int main()
{
    int W, N, w[100], v[100], dp[100][100];
    scanf("%d %d", &W, &N);
    for(int i = 1; i <= N; i++) scanf("%d", &w[i]);
    for(int i = 1; i <= N; i++) scanf("%d", &v[i]);
    memset(dp, 0, sizeof(dp)); // 初始化为0
    /* *********************************************** */
    for(int i = 1; i <= N; i++){
        for(int j = 1; j <= W; j++){
            if(j < w[i]) dp[i][j] = dp[i - 1][j]; //不装,因为装不下
            else dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - w[i]] + v[i]); //在可以装得前提下, 看看装还是不装哪一种情况可以使得价值更大;
        }
    }
    /* ********************************************** */
    printf("%d\n", dp[N][W]);
    return 0;
}
/*
输入:
5 4
2 3
1 2
3 4
2 2
输出:
7
*/

建议大家自己画一个N*W得表格,自己按照程序走一遍,一定会理解得很深刻。
给大家一个栗子,自己比对走一遍也会有不错的效果:

例:0-1背包问题。在使用动态规划算法求解0-1背包问题时,使用二维数组m[i][j]存储背包剩余容量为j,可选物品为i、i+1、……、n时0-1背包问题的最优值。绘制

价值数组v = {8, 10, 6, 3, 7, 2},

重量数组w = {4, 6, 2, 2, 5, 1},

背包容量C = 12时对应的m[i][j]数组。

0 1 2 3 4 5 6 7 8 9 10 11 12
1 0 0 0 8 8 8 8 8 8 8 8 8
2 0 0 0 8 8 10 10 10 10 18 18 18
3 0 6 6 8 8 14 14 16 16 18 18 24
4 0 6 6 9 9 14 14 17 17 19 19 24
5 0 6 6 9 9 14 14 17 17 19 21 24
6 2 6 8 9 11 14 16 17 19 19 21 24

01背包问题的优化

细心的一定发现刚才用的dp数组得空间是一个N*W的,如果N和W都比较大的情况下,有的题目的内存空间不给你很多,如果继续开辟这么大的数组会报内存超限的错误
下面将会说到怎么把N*W的dp 数组压缩成W大小的空间:
我们可以看到,要想得到 dp[i][j],我们需要知道 dp[i - 1][j] 和 dp[i - 1][j - w[i]],由于我们使用二维数组保存中间状态,所以可以直接取出这两个状态。
大家有没有发现第 i 个物品的是由第 i - 1个物品推出来的,也就是刚才让大家比对的那个表格,你要仔细的走了一遍那个表格,一定会发现,第i行的填充是由第i - 1行推出来的,如果建立一个数组dp[ j ] 那么如果当 i = 2,dp[j]数组存储的是当i=1时候&&背包还可承受重量为w时候的价值,那么咱们就可以直接利用dp[j],作为i  - 1时候的价值,但要注意一点,在遍历重量的之后,为了保证当改变dp[j]时,不会影响后面对dp[j]的计算,咱们枚举重量的时候是从大到小枚举的。所以递推方程改为:
1、dp[j] = dp[j]   // 不加入第i个物品
2、dp[j] = dp[j - w[i]] + v[i]  //  加第i个物品
所以:dp[j] = max( dp[j] , dp[j - w[i]] + v[i] )
如果还不理解,一定要!!!自己走一遍代码,其实自己走一遍什么都懂了!!! 自己举个栗子,一点点的走,或者按我刚才列的那个表格按照代码走一遍。

代码:

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
using namespace std;
int main()
{
    int W, N, w[100], v[100], dp[100];
    scanf("%d %d", &W, &N);
    for(int i = 1; i <= N; i++) scanf("%d", &w[i]);
    for(int i = 1; i <= N; i++) scanf("%d", &v[i]);
    memset(dp, 0, sizeof(dp)); // 初始化为0
    /* *********************************************** */
    for(int i = 1; i <= N; i++){
        for(int j = W; j >= w[i]; j--){ //j >= w[i] 保证可以装的下
            dp[j] = max(dp[j], dp[j - w[i]] + v[i]);//在可以装得前提下, 看看装还是不装哪一种情况可以使得价值更大;
        }
    }
    /* ********************************************** */
    printf("%d\n", dp[W]);
    return 0;
}
/*
输入:
5 4
2 3
1 2
3 4
2 2
输出:
7
*/


01背包问题不同条件下的初始化

求最优解的背包问题时,有两种问法:

1、在恰好装满背包的情况下,最多能获得多少价值

2、在不超过背包容量的情况下,最多能获得多少价值

1、在恰好装满背包的情况下,最多能获得多少价值

主要的区别为是否要求恰好装满背包。但这两种问法的实现方法是在初始化的时候有所不同。
恰好装满背包的情况:使用二维数组dp[i][j]存储中间状态,其中第一维表示物品,第二维表示背包容量
初始化时,除了dp[i][0] = 0(第一列)外,其他全为负无穷,这样保证了只有装满的时候才会计算价值。
我们虽然是求恰好装满,还是需要枚举所有可以装入背包的物品,只要能装入,还需装入,收益有增加。只不过,由于恰好装满的物品的序列肯定是从第一列某行开始的,且之后的收益肯定是正值。对于非恰好装满的物品序列,其实位置肯定是从第一行某位置开始的,由于此时被初始化为负无穷,在和那些恰好装满物品序列带来的价值时,肯定是小的。所以,我们最后能获得最大值。

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
using namespace std;
int main()
{
    int W, N, w[100], v[100], dp[100][100];
    scanf("%d %d", &W, &N);
    for(int i = 1; i <= N; i++) scanf("%d", &w[i]);
    for(int i = 1; i <= N; i++) scanf("%d", &v[i]);

    for(int i = 0; i <= N; i++){ //初始化
        for(int j = 1; j <= W; j++){
            dp[i][j] = -9999999;
        }
        dp[i][0] = 0;
    }
    /* *********************************************** */
    for(int i = 1; i <= N; i++){
        for(int j = 1; j <= W; j++){
            if(j < w[i]) dp[i][j] = dp[i - 1][j]; //不装,因为装不下
            else dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - w[i]] + v[i]); //在可以装得前提下, 看看装还是不装哪一种情况可以使得价值更大;
        }
    }
    /* ********************************************** */
    printf("%d\n", dp[N][W]);
    return 0;
}

当用一维数组存储时:

使用一维数组dp[j]存储中间状态,维表示背包容量初始化时,除了dp[0] = 0,其他全为负无穷。原因:只有容量为0 的背包可以什么物品都不装就能装满,此时价值为0。其它容量的背包均没有合法的解,属于未定义的状态,应该被赋值为负无穷了

2、在不超过背包容量的情况下,最多能获得多少价值

全部初始化为0即可。

猜你喜欢

转载自blog.csdn.net/ltrbless/article/details/81486867