动态规划——背包问题

背包问题在以前的时候自己也听过,今天做一下基本的总结;

背包问题目前接触的有两种——01背包问题和完全背包问题;

一、01背包问题:

题目描述如下:有n件物品,每件物品的重量为w[i],价值为c[i],现有一个容量为V的背包,问如何选取物品入背包,使得背包中的物品总价值最大,其中每一个物品都只有一件。

样例:

n=5 v=8

3 5 1 2 2 ——w[i]

4 5 2 1 3 ——c[i]

最简单的思路就是进行暴力枚举,之后进行值判断,但是时间复杂度会达到O(2^n)。

如果用动态规划的思路来做,便会轻松不少。

设立dp数组,因为有重量和价值,所以设置为二维数组。dp[i][v]代表前i件物品可以恰好装入容量为v的背包中所能够获得的最大价值。

由于有n件物品,所以可以从1开始遍历,也就是选择每件物品放还是不放。

对于每一件物品,有两种选择:

1.不放入:则dp相应值求解问题转换为前i-1件物品正好装入容量为v背包所能够获得最大利润。也就是dp[i-1][v];

2.放入:则转化为放入该A[i]物品之前的i-1件物品放入对应容量所能够获得的最大利润,也就是dp[i-1][v-w[i]]+c[i]

此时 dp[i][v]=max{dp[i-1][v],dp[i-v][v-w[i]]+c[i]}

所以获得状态转移方程。

对于dp二维数组来说,可能只看程序不太能理解这个过程。

所以举个例子并且说明几点:

1.对于dp二维数组来说,最终的一行才是整个背包的状态,前面的所有步骤都视为中间推导。

2.对于每一行,其下标作用要理解:下标i代表,如果当前背包容量为i,那么能够装入货物的最大值。

3.第i行代表第i个物品存在时,对于整个背包的影响。

举个例子,如下图所示:

对于图中的几个疑点和困惑,我做了一个思考:

思考问题:如果物件装入顺序发生变化,会不会结果不同?

思考结果:不会。最主要的原因就是每一层状态的转换方式。如核心思想所说,最主要的是i件物品放入不放入的选择,会导致dp的值不同。关键点在于:这是一个动态的转换过程。例如所给例子D,如果将D放入,则剩余的空间为4,问题就完全转换为上一层第三层C的问题,也就是在放入A、B、C的情况下,如果只有四个空间,我们该怎么放。所以这样逐层状态转换,每一层的解必定为最优解,并且不会产生冗余重复计算。并且最后一步max比较,会使得在该条件下达到两种情况的最优解,所以不是什么问题。无论后面有多少个物品想要再次放入,都会有最优解的子问题解决方法,所以不用担心。

对于空间上的优化,我们可以将二维数组变为滚动数组,从而变成一维。从整个程序的进行上来看,我们可以将每一层的循环判定的存储矩阵分为两块,dp[i-1][v],dp[i-v][v-w[i]]。也就是一部分是过去存储的最优dp值,一部分是dp[i-1][v]。所以我们可以设立一个一维数组,然后从右往左遍历,直接覆盖dp[i-1][v]。从而只需要一个固定数组就可以。从而空间负责度变为O(V)。

#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn=100;
const int maxv=1000;
int w[maxn],c[maxn],dp[maxv];
void package(){
    int n,V;
    scanf("%d%d",&n,&V);
    for(int i=1;i<=n;i++){
        scanf("%d",&w[i]);
    }
    for(int i=1;i<=n;i++){
        scanf("%d",&c[i]);
    }
    for(int v=0;v<=V;v++){
        dp[v]=0;
    }
    for(int i=1;i<=n;i++){
        for(int v=V;v>=w[i];v--){
            dp[v]=max(dp[v],dp[v-w[i]]+c[i]);
        }
        printf("%d times\n",i);
        for(int i=1;i<=V;i++){
        printf("%d",dp[i]);
        }
        printf("%d\n",V);
    }
    int max=0;
    for(int v=0;v<=V;v++){
        if(dp[v]>max){
            max=dp[v];
        }
    }
    printf("%d\n",max);
}
int main(){
    package();
    system("pause");
}

二、完全背包问题:

其实完全背包问题唯一的条件改变就是将每一类物品变为无数件,从而可以反复的存取。这个问题的本质还是基于上述01背包的思想。只不过唯一不同的一点是在进行状态转换的时候略有不同。所以,和01背包不同的点也有前i件物品和前i类物品的区别。

对于完全背包问题,第i类物品选择如下:

1.不放入:则dp相应值求解问题转换为前i-1类物品正好装入容量为v背包所能够获得最大利润。也就是dp[i-1][v];

2.放入:由于物品的个数不限,所以可以重复放入,所以状态转换仍为dp[i][],为dp[i][v-w[i]]+c[i]

所以对应的状态转换也可以轻松的变为一维。

for(int i=1;i<=n;i++){
    for(int v=w[i];v<=V;v++){
        dp[v]=max(dp[v],dp[v-w[i]]+c[i]);
    }
}

对于这个状态转换,比较难以理解的在于两点:

1.如何保证添加同类商品不会导致最大值超过v,或者如何保证容量一定呢;

2.会不会因为后续的添加而导致不是最优容量值;

其实最基本的解答还是状态思想。对于每一类物品的重复加入,必定会进行max比较判断。当容量趋于满的时候,无论再怎么加入,都会发现第i类加入和不加入,dp对应的值都是恒定的,此时便会导致容量满而value恒定不变。同理,对于每一类的加入和不加入,其所比较的值都是基于上一个状态的最优解,所以同样不会牵扯到类别加入的先后。

 

以上,是对动态规划——背包问题的初步学习,如果有任何疑问和错误,请给予指正。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值