背包问题

 

1、01背包

        题目:有N件物品和一个容量为V的背包。第i件物品的体积是c[i],价值是w[i],且每种物品只有一件。求解将哪些物品装入背包可使价值总和最大。

        思路:f[i][v]表示前i件物品放入一个容量为v的背包可以获得的最大价值。则其状态转移方程便是:

                                                            f[i][v] = max { f[i-1][v] , f[i-1][v-c[i]] + w[i] }

        核心代码如下:

for (int i = 0; i < c.length; i++) {
    for (int j = 1; j <= V; j++) {
        if (j < c[j]) {
            f[i][j] = f[i - 1][j];
        } else {
            f[i][j] = Math.max(f[i - 1][j], f[i - 1][j - c[i]] + w[i]);
        }
    }
}

        外层循环表示,从第一个物品开始,挨个往背包里放;内层循环表示,当背包容量为j时,放到物品i时所对应的最大价值,最终f[n-1][V]即为所求结果

        上述方法需使用二维数组,但是循环时可以发现,放第i个物品时,其最大价值只与放第i-1个物品的各价值有关,因此无需记录放每个物品时的最大价值,使用一维数组存储不同背包容量对应的最大价值,每次放入物品时更新此一维数组即可。

        代码如下:

for (int i = 0; i < c.length; i++) {
    for (int j = V; j >= c[i]; j--) {
        f[j] = Math.max(f [j], f [j - c[i]] + w[i]);
    }
}

        内层循环从最大容量V开始是因为,如果从容量为1开始遍历,放物品的时候,某个物品有可能被放多次。例如,某物品体积为1,价值为2,如果从容量为1开始计算,那么f[1]=2,f[2]=4,f[3]=6;如果从容量为V开始计算,那么f[3]=2,f[2]=2,f[1]=2。假设当前放入的物品为i,从V开始遍历的话,f[j]之前的结果都是放入物品i-1后所得到的最大值,可以保证递推公式无误;当从1开始遍历的话,f[j]之前的结果都是已经放入物品i之后更新的结果,不能满足递推公式,造成错误。

        循环到c[i]为止是因为,如果背包的容量小于当前物品的体积,那么物品将不会被放入背包中,则最大价值不会改变,无需更新f数组。

        初始化:如果要不需要恰好装满,则f数组全部初始化为0;如果需要恰好装满,则f数组除了f[0]初始化为0外,其余全部初始化为-∞

2、完全背包

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

        思路:f[i][v]表示前i件物品放入一个容量为v的背包可以获得的最大价值。则其状态转移方程便是:

                                                            f[i][v] = max{ f[i-1][v-k*c[i]] + k*w[i] },  0 <= k*c[i] <=v

        优化:完全背包问题有一个很简单有效的优化,是这样的:若两件物品i、j满足c[i]<=c[j]且w[i]>=w[j],则将物品j去掉,不用考虑。这个优化的正确性显然:任何情况下都可将价值小费用高得j换成物美价廉的i,得到至少不会更差的方案。对于随机生成的数据,这个方法往往会大大减少物品的件数,从而加快速度。然而这个并不能改善最坏情况的复杂度,因为有可能特别设计的数据可以一件物品也去不掉。

        这个优化可以简单的O(N^2)地实现,一般都可以承受。另外,针对背包问题而言,比较不错的一种方法是:首先将费用大于V的物品去掉,然后使用类似计数排序的做法,计算出费用相同的物品中价值最高的是哪个,可以O(V+N)地完成这个优化

        转化成01背包问题:既然01背包问题是最基本的背包问题,那么我们可以考虑把完全背包问题转化为01背包问题来解。最简单的想法是,考虑到第i种物品最多选V/c[i]件,于是可以把第i种物品转化为V/c[i]件费用及价值均不变的物品,然后求解这个01背包问题。这样完全没有改进基本思路的时间复杂度,但这毕竟给了我们将完全背包问题转化为01背包问题的思路:将一种物品拆成多件物品。

        更高效的转化方法是:把第i种物品拆成费用为c[i]*2^k、价值为w[i]*2^k的若干件物品,其中k满足c[i]*2^k<=V。这是二进制的思想,因为不管最优策略选几件第i种物品,总可以表示成若干个2^k件物品的和。这样把每种物品拆成O(log(V/c[i]))件物品,是一个很大的改进。

        虽然完全背包问题可以转化成01背包问题,但是这种方法的复杂度仍然很高(>>N*V),下列方法可以使复杂度为N*V

        核心代码如下:

for (int i = 0; i < c.length; i++) {
    for (int j = c[i]; j <= V; j++) {
        f[j] = Math.max(f[j], f[j - c[i]] + w[i]);
    }
}

        内层循环与01背包问题的遍历方向恰好相反,01背包问题不能放重复的物品,而完全背包问题可以放重复的物品,从背包容量为c[i]开始遍历,恰好可以完成重复放物品的功能。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值