动态规划初步之背包问题(参考背包九讲+例题+详细分析+补充)

1 01背包问题
1.1 题目
有N件物品和一个容量为V 的背包。放入第i件物品耗费的空间是Ci,得到 的价值是Wi。求解将哪些物品装入背包可使价值总和最大。
1.2 基本思路
这是最基础的背包问题,特点是:每种物品仅有一件,可以选择放或不 放。 用子问题定义状态:即F[i,v]表示前i件物品恰放入一个容量为v的背包可以 获得的最大价值。则其状态转移方程便是:

F[i,v] = max{F[i−1,v],F[i−1,v−Ci] + Wi} 

这个方程非常重要,基本上所有跟背包相关的问题的方程都是由它衍生 出来的。所以有必要将它详细解释一下:“将前i件物品放入容量为v的背包中”这个子问题,若只考虑第i件物品的策略(放或不放),那么就可以转化 为一个只和前i−1件物品相关的问题。如果不放第i件物品,那么问题就转化 为“前i−1件物品放入容量为v的背包中”,价值为F[i−1,v];如果放第i件物 品,那么问题就转化为“前i−1件物品放入剩下的容量为v −Ci的背包中”, 此时能获得的最大价值就是F[i−1,v −Ci]再加上通过放入第i件物品获得的价 值Wi。 伪代码如下:
 

F[0,0..V ] = 0 
for i = 1 to N 
    for v = Ci to V 
        F[i,v] = max{F[i−1,v],F[i−1,v−Ci] + Wi}

例:在使用动态规划算法求解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

第一行和第一列为序号,其数值为0)
如m[2][6],在面对第二件物品,背包容量为6时我们可以选择不拿,那么获得价值仅为第一件物品的价值8,如果拿,就要把第一件物品拿出来,放第二件物品,价值10,那我们当然是选择拿。m[2][6]=m[1][0]+10=0+10=10;依次类推,得到m[6][12]就是考虑所有物品,背包容量为C时的最大价值。

#include <iostream>
#include <cstring>
using namespace std;
 
 
const int N=15;
 
 
int main()
{
    int v[N]={0,8,10,6,3,7,2};
    int w[N]={0,4,6,2,2,5,1};
 
 
    int m[N][N];
    int n=6,c=12;
    memset(m,0,sizeof(m));
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=c;j++)
        {
            if(j>=w[i])
                m[i][j]=max(m[i-1][j],m[i-1][j-w[i]]+v[i]);
 
 
            else
                m[i][j]=m[i-1][j];
        }
    }
 
 
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=c;j++)
        {
            cout<<m[i][j]<<' ';
        }
        cout<<endl;
    }
 
 
    return 0;
}

1.3 优化空间复杂度
以上方法的时间和空间复杂度均为O(V N),其中时间复杂度应该已经不能 再优化了,但空间复杂度却可以优化到O(V )。 先考虑上面讲的基本思路如何实现,肯定是有一个主循环i = 1..N,每次 算出来二维数组F[i,0..V ]的所有值。那么,如果只用一个数组F[0..V ],能不 能保证第i次循环结束后F[v]中表示的就是我们定义的状态F[i,v]呢?F[i,v]是 由F[i−1,v]和F[i−1,v−Ci]两个子问题递推而来,能否保证在推F[i,v]时(也 即在第i次主循环中推F[v]时)能够取用F[i−1,v]和F[i−1,v −Ci]的值呢?事 实上,这要求在每次主循环中我们以v = V..0的递减顺序计算F[v],这样才能保 证推F[v]时F[v−Ci]保存的是状态F[i−1,v−Ci]的值。伪代码如下:

F[0..V ] = 0 
for i = 1 to N 
    for v = V to Ci 
        F[v] = max{F[v],F[v−Ci] + Wi}

其中的F[v] = max{F[v],F[v −Ci] + Wi}一句,恰就对应于我们原来的转移方 程,因为现在的F[v−Ci]就相当于原来的F[i−1,v−Ci]。如果将v的循环顺序 从上面的逆序改成顺序的话,那么则成了F[i,v]由F[i,v−Ci]推导得到,与本题 意不符。 事实上,使用一维数组解01背包的程序在后面会被多次用到,所以这里抽象 出一个处理一件01背包中的物品过程,以后的代码中直接调用不加说明。

 有了这个过程以后,01背包问题的伪代码就可以这样写:

for i = 1 to N 
    for v = V to C 
        F[v] = max(F[v],f[v−C] + W)


1.4 初始化的细节问题
我们看到的求最优解的背包问题题目中,事实上有两种不太相同的问法。 有的题目要求“恰好装满背包”时的最优解,有的题目则并没有要求必须把背 包装满。一种区别这两种问法的实现方法是在初始化的时候有所不同。

如果是第一种问法,要求恰好装满背包,那么在初始化时除了F[0]为0,其 它F[1..V ]均设为−∞,这样就可以保证最终得到的F[V ]是一种恰好装满背包的 最优解。 如果并没有要求必须把背包装满,而是只希望价格

  • 5
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值