背包问题【01、完全(恰好or不超过)、多重】【尚未整理完】

变形问题:2N长度数组,分成俩N长度的,使其和差值最小。【程序员面试100题之十五:数组分割

 

 

 

(写在前面)背包问题 总结的要点:

(黄色字体标题的代码为三个问题最终用的模板)

1.一维(矩阵)的01背包为啥要逆序(保证更新f[j]时,f[ j - weight[i] ]是没有放入物品i时的数据即f[ i - 1 ][ j - weight[i] ],因为01背包每个物品至多被选一次。而完全背包中,每个物品可以被选无限次,那么状态f[i][j],正好可以由可能已经放入物品i的状态f[ i - 1 ][ j - weight[i] ]转移而来。所以,遍历顺序改为顺序时,就是完全背包问题,其余都不用变~)

 

2.完全背包 和 01背包 的区别在于状态更新时的遍历顺序。(即01是逆序,完全是顺序)

 

3.不超过容量 和 恰好装满 的区别在于二者的初始化~(前者全0;后者,f[i][0] = 0(第一列),其余全为0x80000000,一维:除f[0]为0外,其余f[j]都是负无穷)

 

4.循环边界  i:【1,N】,j:【V,weight[i]】(下文有讲,还有更优化的..不过看不懂)

 

5.多重背包问题可以拆分为 01背包 与 完全背包 的组合。

 

6.打印出装的顺序(下文有讲 尚未看)

 

 

 

原文说的很详细甚至到了啰嗦的地步..持续整理中...= =b

 

以下整理自:背包问题九讲笔记_01背包

 

 

摘自Tianyi Cui童鞋的《背包问题九讲》,稍作修改,方便理解。

01背包问题描述

已知:有一个容量为V的背包和N件物品,第i件物品的重量是weight[i],收益是value[i]。

限制:每种物品只有一件,可以选择放或者不放

问题:在不超过背包容量的情况下,最多能获得多少价值或收益

变型问题:在恰好装满背包的情况下,最多能获得多少价值或收益

这里,我们先讨论在不超过背包容量的情况下,最多能获得多少价值或收益。

基本思路分析

考虑我们的子问题,将前i件物品放到容量为v的背包中,若我们只考虑第i件物品时,它有两种选择,放或者不放。

1) 如果第i件物品不放入背包中,那么问题就转换为:将前i - 1件物品放到容量为v的背包中,带来的收益f[i - 1][v]

2) 如果第i件物品能放入背包中,那么问题就转换为:将前i - 1件物品放到容量为v - weight[i]的背包中,带来的收益f[i - 1][v - weight[i]] + value[i]

二维矩阵的实现代码(非最佳,因为可以使用一维矩阵,从而降低空间复杂度)

#include <iostream>  
using namespace std;  
  
const int N = 3;//物品个数  
const int V = 5;//背包最大容量  
int weight[N + 1] = {0,3,2,2};//物品重量  
int value[N + 1] = {0,5,10,20};//物品价值  
  
int f[N + 1][V + 1] = {
  {0}}; 
  
/* 
目标:在不超过背包容量的情况下,最多能获得多少价值 
 
子问题状态:f[i][j]:表示前i件物品放入容量为j的背包得到的最大价值 
 
状态转移方程:f[i][j] = max{f[i - 1][j],f[i - 1][j - weight[i]] + value[i]} 
 
初始化:f数组全设置为0 
*/  
int ZeroOnePack()  
{  
    //初始化  
    memset(f,0,sizeof(f));  
    //递推  
    for (int i = 1;i <= N;i++) //枚举物品。i为0时无意义,故f[0][j] = 初始值0  
    {  
        for (int j = V;j >= 0;j--) //枚举背包容量  
        {  
            f[i][j] = f[i - 1][j];  //j<weight[i]时,状态仅能来自于<span style="font-family: Arial, Helvetica, sans-serif;">f[i - 1][j]</span>
            if (j >= weight[i])  
            {  
                f[i][j] = max(f[i - 1][j],f[i - 1][j - weight[i]] + value[i]);  
            }  
        }  
    }  
    return f[N][V];  
}  

 

 

 

 

 

 copy

效率分析:

此算法的时间复杂度为O(N*V),空间复杂度也为O(N*V)。其中,N 表示物品个数,V 表示背包容量。

这里,时间复杂度不可以在优化了,但是空间复杂度可以继续优化到O(V)。

优化空间复杂度

上述的方法,我们使用二维数组 f[i][v] 保存中间状态,这里我们可以使用一维数组f[v]保存中间状态就能得到结果。

分析

f[i][j] = max(f[i - 1][j],f[i - 1][j - weight[i]] + value[i]),我们可以看出,状态的转移仅与上一行(i-1行)有关。

我们现在使用f[v]保存中间状态,我们想要达到的效果是,第i次循环后,f[v]中存储的是前i个物体放到容量v时的最大价值

当我们使用一维数组存储状态时,f[v]表示,在执行i次循环后(此时已经处理i个物品),前i个物体放到容量v时的最大价值,即之前的f[i][v]。与二维相比较,它把第一维隐去了,但是二者表达的含义还是相同的,只不过针对不同的i,f[v]一直在重复使用,所以,也会出现第i次循环可能会覆盖第i - 1次循环的结果。

为了求f[v],我们需要知道,前i - 1个物品放到容量v的背包中带来的收益,即之前的f[i - 1][v] 和 前i - 1件物品放到容量为v - weight[i]的背包中带来的收益,即之前的f[i - 1][v - weight[i]] + value[i]。

难点

由于我们只使用一维数组存储,则在求这两个子问题时就没有直接取出那么方便了,因为,第i次循环可能会覆盖第i - 1次循环的结果。

现在我们来求这两个值:

1)前i-1个物品放到容量v的背包中带来的收益,即之前的f[i - 1][v] :

由于,在执行在i次循环时,f[v]存储的是前i个物体放到容量v时的最大价值,在求前i个物体放到容量v时的最大价值(即之前的f[i][v])时,我们是正在执行第i次循环,f[v]的值还是在第i-1次循环时存下的值,在此时取出的 f[v]就是前i-1个物体放到容量v时的最大价值,即f[i - 1][v]。

2)前i-1件物品放到容量为v-weight[i]的背包中带来的收益,即之前的f[i-1][v-weight[i]] + value[i]

由于,在执行第i次循环前,f[0] ~ f[V]中保存的是第i - 1次循环的结果,即是前i - 1个物体分别放到容量0 ~ V时的最大价值,即f[i - 1][0] ~f[i - 1][V]。

则,在执行第i次循环前,f 数组中v - weight[i]的位置存储就是我们要找的 "前i - 1件物品放到容量为v - weight[i]的背包中带来的收益" (即之前的f[i - 1][v - weight[i]]),这里假设物品是从数组下标1开始存储(全文皆是)。

伪代码

 

for i=1..N //枚举物品  
    for v=V..0 //枚举容量,从大到小  
        f[v]=max{f[v],f[v-weight[i]] + value[i]};  

 

由上面伪代码可知,在执行第 i 次循环时,

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值