【算法】01背包

先看题目

物品不能分隔,必须全部取走或者留下,因此称为01背包
(只有不取和取两种状态)

看第一个样例

我们需要把4个物品装入一个容量为10的背包

我们可以简化问题,从小到大入手分析

weight	value
2		1
3		3
4		5
7		9

先考虑物品数量为1的情况:

把前1件物品放入容量为1的背包,此时得到的最大价值是:0
把前1件物品放入容量为2的背包,此时得到的最大价值是:1
把前1件物品放入容量为3的背包,此时得到的最大价值是:1
......
把前1件物品放入容量为10的背包,此时得到的最大价值是:1

把前2件物品放入容量为1的背包,此时得到的最大价值是:0
把前2件物品放入容量为2的背包,此时得到的最大价值是:1
把前2件物品放入容量为3的背包,此时得到的最大价值是:3
把前2件物品放入容量为4的背包,此时得到的最大价值是:3
把前2件物品放入容量为5的背包,此时得到的最大价值是:4
把前2件物品放入容量为6的背包,此时得到的最大价值是:4
......
把前2件物品放入容量为10的背包,此时得到的最大价值是:4

把前3件物品放入容量为1的背包,此时得到的最大价值是:0
把前3件物品放入容量为2的背包,此时得到的最大价值是:1
把前3件物品放入容量为3的背包,此时得到的最大价值是:3
把前3件物品放入容量为4的背包,此时得到的最大价值是:5
把前3件物品放入容量为5的背包,此时得到的最大价值是:5
把前3件物品放入容量为6的背包,此时得到的最大价值是:6
......
把前3件物品放入容量为10的背包,此时得到的最大价值是:9

即:先从物品的数量开始枚举,对i件物品枚举所对应的背包容量j,考虑它的最大价值f[i][j]

for(int i=1;i<=n;i++){//总共有n件物品
  for(int j=1;j<=m;j++){//背包的最大容量是m
    
  }
}

对于一个f[i][j]:

(1)假设我们不选择第i件物品,则f[i][j] = f[i-1][j],相当于把前i-1件物品放入这个容量为j的背包,得到的最大值;

(2)假设我们选择第i件物品,则这个物品剩余的空间就是j-w[i],前一次循环的时候必定会算出这个值来,因此可以让:f[i][j] = f[i-1][j-w[i]] + v[i]

两种选择中,我们保留最优解,避免下标出现负数(即j-w[i]可能为负数)

for(int i=1;i<=n;i++){
      for(int j=1;j<=m;j++){
            f[i][j]=f[i-1][j];
            if(j>=w[i])f[i][j]=max(f[i][j],f[i-1][j-w[i]]+v[i]);
      }
}
printf("%d\n",f[n][m]);

其实这道题还有更多的做法,例如递归,从大到小的做法

上面的做法其实是递推,时间复杂度是O(n*m),相比递归好一点,使用这种算法只要保证n*m在107以内即可。空间复杂度则是O(n*m)

其实上,f数组是可以压缩成一维数组的

对于一个数f[i][j],实际上是只用到了第i-1行

因此,经过简单的优化,只需要一行即可,把上一行的信息移动下来,删掉所有相关第一维度的东西,看看会变成什么样:

f[j]=f[j-w[i]]; //直接存到下一行中
f[j]=f[j];
f[j]=max(f[j],f[j-w[i]]);

对于一维数组的问题所在:循环的顺序会发生变化,如果继续按照从左到右的顺序进行循环的话,我们会发现,每一个数字都会被后面的数据所引用,因此需要从后往前来递推。这样,后面的值更新的时候不会被第二次使用,更新前面的值都不会被影响到。

反正记住是从后往前就行了

完整版的代码

for(int i=1;i<=n;i++){
    for(int j=m;j>=w[i];j--){
        //此处写j>=w[i]避免下标产生负数
        //相当于if(j>=w[i]) f[j]=max(f[j],f[j-w[i]]+v[i]);
        f[j]=max(f[j],f[j-w[i]]+v[i]);
    }
}
posted @ 2022-04-19 11:50  计算机知识杂谈  阅读(323)  评论(0编辑  收藏  举报