动态规划— 0-1背包问题

什么是0_1背包问题:     0-1背包问题,表示的是每个物品只有一,每件物品不能分割,在不超过背包容量的同时,如何选取物品,使得背包所装的价值最大(背包可以不装满)。

特点:“步步优"。即最优解是由部分最优解得来。

 递归地定义最优解的值:

  对于每个物品我们可以有两个选择,放入背包,或者不放入,有n个物品,故而我们需要做出n个选择,于是我们设f[i][v]表示做出第i次选择后,所选物品放入一个容量为v的背包获得的最大价值。对于第i件物品,有两种选择,放或者不放。

  1:如果放入第i件物品,则f[i][v] = f[i-1][v-w[i]]+p[i],表示,前i-1次选择后所选物品放入容量为v-w[i]的背包所获得最大价值为f[i-1][v-w[i]],加上当前所选的第i个物品的价值p[i]即为f[i][v]。

  2:如果不放入第i件物品,则有f[i][v] = f[i-1][v],表示当不选第i件物品时,f[i][v]就转化为前i-1次选择后所选物品占容量为v时的最大价值f[i-1][v]。则:f[i][v] = max{f[i-1][v], f[i-1][v-w[i]]+p[i]

  注意这里f[i][v](i指的是前i件物品。v指的是背包剩余容量)

例如:有 N 件物品和一个容量为 V 的背包。第 i 件物品的费用是 w[i],价值是 p[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。

这里假设N = 5,V = 10, w[] = {2,2,6,5,4},p[] = {6,3,5,4,6}。

至此可以写出代码来:

#include <iostream>  
#include <cstdio>  
#include <cstdlib>  
using namespace std;  
const int MAXN = 100;  
  
int main()  
{  
    int n,V;  
    int f[MAXN][MAXN];        //例如:f[i][j]表示选择第i件物品后,剩余背包容量为j时的价值。 
    int w[MAXN], p[MAXN];    //w记录物品的重量,p记录物品价值。
    while(cin>>n>>V)  
    {  
        if(n == 0 && V == 0)  
            break;  
        for(int i = 0; i <= n; i++)  
        {  
            w[i] = 0, p[i] = 0;  
            for(int j = 0; j <= n; ++j)  
            {  
                f[i][j] = 0;  
            }  
        }  
        for(int i = 1; i <= n; ++i)  
            cin>>w[i]>>p[i];    
        /*这里包含对i-n件物品,容量分别为它的w[i]到总容量V区间的选择情况*/
        for(int i = 1; i <= n; ++i)  
        {  
            for(int t = w[i]; t <= V; t++) 
            {  
                f[i][t] = max(f[i-1][t], f[i-1][t-w[i]]+p[i]);  
            }  
        }  
        cout<<f[n][V]<<endl;  
    }  
    return 0;  
}  

优化情况:我们可以知道,我们必须要做出n次选择,所以外层n次循环是必不可少的,对于上面代码的内层循环,表示当第i个商品要放入容量为v(v = w[i]....V)的背包时所获得的价值,即先对子问题求解,这个也是必不可少的,所以时间复杂度为O(nV),这个已不能进一步优化,但是我们可以对空间进行优化。

  由于我们用f[n][V]表示最大价值,但是当物品和背包容量比较大时,这种用法会占用大量的空间,那么我们是不是对此可以进一步优化呢?

  现在考虑如果我们只用一位数组f[v]来表示f[i][v],是不是同样可以达到效果?我们由上述可知f[i][v]是由f[i-1][v]和f[i-1][v-w[i]]这两个子问题推得,事实上第i次选择时,我们虽用到前i-1次的最优结果,但是前i-1次选择的最优结果,已经保存在做出第i-1次选择后的结果中,即第i次的结果只用到了第i-1次选择后的状态因此我们可以只用一维数组来维持每次选择的结果,怎么维持?也就是当第i次选择时,我们怎么得到f[i-1][v]和f[i-1][v-w[j]]这两种状态,即第i次求f[v]时,此时f[v]和f[v-w[i]]表示的是不是f[i-1][v]和f[i-1][v-w[j]]事实上我们只需要将内层循环变为从V到w[j]的逆向循环即可满足要求。

代码:

#include <iostream>  
#include <cstdio>  
#include <cstdlib>  
#include <cstring>  
using namespace std;  
  
const int MAXN = 100;  
  
int main()  
{  
    int n, V;  
    int f[MAXN];  
    int w[MAXN], p[MAXN];  
  
    while(cin>>n>>V)  
    {  
        if(n == 0 && V == 0)  
            break;  
        memset(f, 0, sizeof(f));   //初始化为0
        memset(w, 0, sizeof(w));  
        memset(p, 0, sizeof(p));  
        for(int i = 1; i <= n; ++i)  
            cin>>w[i]>>p[i];  
        for(int i = 1; i <= n; ++i)  
        {  
            for(int v = V; v >= w[i]; --v)  //这里要注意,要从后往前推.for(int v = w[i]; v <= V; ++v)不可。
            {                               //因为假设此时我们用的f[6]和f[3]相当于是f[1][6]和f[1][3],而不是f[0][6]和f[0][3].因为我们在上述代码中隐含对[0-n]的 二维数组情况.
                f[v] = max(f[v], f[v-w[i]]+p[i]);  
            }  
        }  
        cout<<f[V]<<endl;  
    }  
    return 0;  
} 
参考博客: 点击打开链接
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值