01背包问题中,优化空间中采用逆序(从大到小)枚举的个人思考

例题参考01背包问题,文章从头分析01背包问题,较长。

01背包问题中,如果不考虑空间优化,则从大到小或从小到大枚举都可以;
如果考虑优化,则需要从大到小枚举。

题目描述

有一个容量为 V 的背包,和一些物品。这些物品分别有两个属性,体积 w 和价值 v,每种物品只有一个。要求用这个背包装下价值尽可能多的物品,求该最大价值,背包可以不被装满。

状态为dp[i][j],表示前i个物品放入容量为j时达到的最大价值;

状态转移方程为:

  • j<w,dp[i][j] = dp[i-1][j] //背包装不下该物品,最大价值不变
  • j>=w, dp[i][j] = max{ dp[i-1][j-list[i].w] + list[i].v, dp[i-1][j] } //和不放入该物品时同样达到该体积的最大价值比较

其中,list[i].w表示第i件物品的体积,list[i].v表示第i件物品的价值

1.1、不考虑优化的从小到大枚举

#include<cstdio>

int max(int a, int b)//取最大值函数
{
    return a > b ? a : b;
}

struct Thing
{
    int w;
    int v;
}list[101];

int dp[101][1001];

int main()
{
    int s, n;//背包容量和物品总数
    while (scanf("%d%d", &s, &n) != EOF)
    {
        for (int i = 1; i <= n; i++)
        {
            scanf("%d%d", &list[i].w, &list[i].v);//读入每个物品的体积和价值
        }
        for (int i = 0; i <= s; i++) dp[0][i] = 0;//初始化二维数组
        for (int i = 1; i <= n; i++)//循环每个物品,执行状态转移方程
        {
            for (int j = 0; j <= s; j++)
            {
                if (j >= list[i].w)dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - list[i].w] + list[i].v);
                else dp[i][j] = dp[i - 1][j];
            }
        }
        printf("%d\n", dp[n][s]);
    }
    return 0;
}

1.2、考虑优化的从大到小的枚举

#include<cstdio>

int max(int a, int b)//取最大值函数
{
    return a > b ? a : b;
}

struct Thing
{
    int w;
    int v;
}list[101];

int dp[1001];

int main()
{
    int s, n;//背包容量和物品总数
    while (scanf("%d%d", &s, &n) != EOF)
    {
        for (int i = 1; i <= n; i++)
        {
            scanf("%d%d", &list[i].w, &list[i].v);//读入每个物品的体积和价值
        }
        for (int i = 0; i <= s; i++) dp[i] = 0;//初始化一维数组
        for (int i = 1; i <= n; i++)//循环每个物品,逆序遍历j执行状态转移方程
        {
            for (int j = s; j >= list[i].w; j--)
            {
                dp[j] = max(dp[j], dp[j - list[i].w] + list[i].v);
            }
        }
        printf("%d\n", dp[s]);
    }
    return 0;
}

举例假设

现在,我们来举例说明一下优化代码中j从大到小枚举和从小到大枚举的区别:

2.1、对于从小到大枚举的代码

            for (int j =list[i].w; j <= s; j--)
            {
                dp[j] = max(dp[j], dp[j - list[i].w] + list[i].v);
            }


假设s=8; 当i = 1时,list[i].w=4;一次得到dp[j]的值如下:

  • dp[4] = max(dp[4], dp[0]+list[i].v)
  • dp[5] = max(dp[5], dp[1]+list[i].v)
  • dp[6] = max(dp[6], dp[2]+list[i].v)
  • dp[7] = max(dp[7], dp[3]+list[i].v)
  • dp[8] = max(dp[8], dp[4]+list[i].v)

在计算dp[8]时,dp[8]=dp[4]+list[i].v,而在前面计算dp[4]时,已经计算得到dp[4]=dp[0]+list[i].v=list[i].v。

所以这样计算出来的dp[8] = 2*list[i].v,不满足题目中每个物品只有一个的要求;
 

2.2、如果是从大到小的枚举代码

            for (int j =s; j >= list[i].w]; j--)
            {
                dp[j] = max(dp[j], dp[j - list[i].w] + list[i].v);
            }


当i=1时,一次得到dp[j]的值如下:

  • dp[8] = max(dp[8], dp[4]+list[1].v)=list[1].v
  • dp[7] = max(dp[7], dp[3]+list[1].v)=list[1].v
  • dp[6] = max(dp[6], dp[2]+list[1].v)=list[1].v
  • dp[5] = max(dp[5], dp[1]+list[1].v)=list[1].v
  • dp[4] = max(dp[4], dp[0]+list[1].v)=list[1].v

当i=2时,一次得到dp[j]的值如下:

  • dp[8] = max(dp[8], dp[4]+list[2].v)=max(list[1].v,  dp[4]+list[2].v)=max(list[1].v,  list[1].v+list[2].v)
  • dp[7] = max(dp[7], dp[3]+list[2].v)=max(list[1].v,  dp[3]+list[2].v)
  • dp[6] = max(dp[6], dp[2]+list[2].v)=max(list[1].v,  dp[2]+list[2].v)
  • dp[5] = max(dp[5], dp[1]+list[2].v)=max(list[1].v,  dp[1]+list[2].v)
  • dp[4] = max(dp[4], dp[0]+list[2].v)=max(list[1].v,  dp[0]+list[2].v)

思考分析

在优化解法中,如果从小到大顺序遍历所有j的值,则在更新dp[j]时会出现某个物品被重复使用的问题;只有倒序遍历所有j的值,这样在更新dp[j]的时候,dp[j-list[i].w]的值尚未被修改,才能保证每个物品只被使用过一次。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值