关闭

【DP算法篇之初学】背包问题

标签: 背包问题动态规划dp
3639人阅读 评论(1) 收藏 举报
分类:

昨天做了爱奇艺的内推笔试,编程题又出现了动态规划问题,感觉动态规划出现的概率好大,需要加强下。这里借用背包问题开始我们的学习。


背包问题的经典讲解可以参见背包问题九讲,此外我在刷题的过程中发现还发现了背包六问





0 1 背包


最经典的 01 背包问题可以描述为:

有n个物品,每个物品的重量为w[i],每个物品的价值为v[i]。现在有一个背包,它所能容纳的重量为W,问:当你面对这么多有价值的物品时,你的背包所能带走的最大价值是多少?


思路:每个物品无非是装入背包或者不装入背包,那么就一个一个物品陆续放入背包中

我们用c[i][w]表示处理到第i 个物品时背包容量为w下所能装下的最大价值。关键的状态转移方程如下:



伪代码如下:




优化空间复杂度


以上方法的时间和空间复杂度均为O(Wn),其中时间复杂度应该已经不能再优化了,但空间复杂度却可以优化到O(W)。


先考虑上面讲的基本思路如何实现,肯定是有一个主循环i = 1..n,每次算出来二维数组 c[i][0..W] 的所有值。那么,如果只用一个数

c[0..W],能不能保证第 i 次循环结束后 c[w] 中表示的就是我们定义的状态 c[i][w] 呢? c[i][w] 是由 c[i - 1][w] 和 c[i-1][w - c[i]] 两个

问题递推而来,能否保证在推 c[i][w] 时(也即在第 i 次主循环中推 c[w] 时)能够得到 c[i - 1][w] 和 c[i - 1][w - c[i]] 的值呢?事实

上,这要求在每次主循环中我们以 w=W..0的顺序推 c[w],这样才能保证推 c[w] 时 c[w - c[i]] 保存的是状态 c[i - 1][w - c[i]] 的值。伪

代码如下:

for i = 1..n
    for w = W..0
        c[w] = max{c[w], c[w - w[i]] + v[i]};

其中的 c[v] = max{c[w], c[w - c[i]]}一句恰就相当于我们的转移方程 c[i][w] = max{c[i - 1][w], c[i - 1][w - c[i]]},因为现在的 c[w - c[i]]就

相当于原来的 c[i - 1][w - c[i]]。如果将 v 的循环顺序从上面的逆序改成顺序的话,那么则成了 c[i][w] 由 c[i][w - c[i]] 推知,与本题意不

符,但它却是完全背包问题最简捷的解决方案,故学习只用一维数组解01背包问题是十分必要的。


事实上,使用一维数组解01背包的程序在后面会被多次用到,所以这里抽象出一个处理一件01背包中的物品过程,以后的代码中直接调用不加说明。

过程ZeroOnePack,表示处理一件01背包中的物品,两个参数weight、value分别表明这件物品的重量和价值。

procedure ZeroOnePack(weight, value)
    for w = W..weight
        c[w] = max{c[w], c[w - weight] + value}

注意这个过程里的处理与前面给出的伪代码有所不同。前面的示例程序写成 w = W..0是为了在程序中体现每个状态都按照方程求解

了,避免不必要的思维复杂度。而这里既然已经抽象成看作黑箱的过程了,就可以加入优化。费用为cost的物品不会影响状态c[0..weight - 1],这是显然的。

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

for i=1..N
    ZeroOnePack(w[i], v[i]);





完全背包



基本思路


这个问题非常类似于01背包问题,所不同的是每种物品有无限件。也就是从每种物品的角度考虑,与它相关的策略已并非取或不取两种,而是有取0件、取1件、取2件……等很多种。如果仍然按照解01背包时的思路,令 c[i][w] 表示前 i 种物品恰放入一个容量为w的背包的最大权值。仍然可以按照每种物品不同的策略写出状态转移方程,像这样:


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


这跟01背包问题一样有O(Wn)个状态需要求解,但求解每个状态的时间已经不是常数了,求解状态 c[i][w] 的时间是O(w / w[i]),总的复杂度可以认为是O(W * Σ(W / w[i])),是比较大的。


将01背包问题的基本思路加以改进,得到了这样一个清晰的方法。这说明01背包问题的方程的确是很重要,可以推及其它类型的背包问题。但我们还是试图改进这个复杂度。


O(Wn)的算法


这个算法使用一维数组,先看伪代码:

for i = 1..n
    for w = 0..W
        c[w]=max{c[w], c[w - w[i]] + v[i]}

你会发现,这个伪代码与上面01背包的伪代码只有w的循环次序不同而已。为什么这样一改就可行呢?首先想想为什么01背包中要

按照 w = W..0 的逆序来循环。这是因为要保证第 i 次循环中的状态 c[i][w] 是由状态 c[i - 1][w - w[i]] 递推而来。换句话说,这正是为

了保证每件物品只选一次,保证在考虑“选入第i件物品”这件策略时,依据的是一个绝无已经选入第 i 件物品的子结果                         

 c[i - 1][w - w[i]] 。而现在完全背包的特点恰是每种物品可选无限件,所以在考虑“加选一件第 i 种物品”这种策略时,却正需要一个可

能已选入第 i 种物品的子结果 c[i][w - w[i]],所以就可以并且必须采用 w = 0..W的顺序循环。这就是这个简单的程序为何成立的道

理。

最后抽象出处理一件完全背包类物品的过程伪代码:

procedure CompletePack(weight, value)
    for w = weight..W
        c[w] = max{c[w], c[w - weight] + value}





多重背包


可以参见:多重背包

先在这挖个坑,以后遇到了再来。。




下面实战一下背包问题,题目均来自 lintcode 。




1、Backpack


题目叙述:

Given n items with size Ai, an integer m denotes the size of a backpack. 
How full you can fill this backpack?

Example
If we have 4 items with size [2, 3, 5, 7], the backpack size is 11, we can select [2, 3, 5], 
so that the max size we can fill this backpack is 10. If the backpack size is 12. 
we can select [2, 3, 7] so that we can fulfill the backpack.

You function should return the max size we can fill in the given backpack.



题目分析:

该问题是 01 背包问题的简化版。


依照题目我们可以得到状态转换方程:

res[i][j] = max{res[i - 1][j - w[i]] + w[i], res[i - 1][j]}


同理我们也可以按照 01背包的空间复杂度优化方法优化如下:

res[j] = max{res[j - w[i]] + w[i], res[j]}


代码如下:

class Solution {
public:
    /**
     * @param m: An integer m denotes the size of a backpack
     * @param A: Given n items with size A[i]
     * @return: The maximum size
     */
    int backPack(int m, vector<int> A) {
        // write your code here
        int n = A.size(), i, j;
        vector<int> res(m + 1);
    
        for(i = 0; i < n; ++i)
        {
            for(j = m; j >= A[i]; --j)
            {
                res[j] = max(res[j - A[i]] + A[i], res[j]);
            }
        }
        
        return res[m];
    }
};



2、Backpack II


题目描述:

Given n items with size Ai and value Vi, and a backpack with size m. 
What's the maximum value can you put into the backpack?

Example
Given 4 items with size [2, 3, 5, 7] and value [1, 5, 2, 4], and a backpack with size 10. The maximum value is 9.




题目分析:

该题目就是我们的 01 背包问题。

代码如下:

class Solution {
public:
    /**
     * @param m: An integer m denotes the size of a backpack
     * @param A & V: Given n items with size A[i] and value V[i]
     * @return: The maximum value
     */
    int backPackII(int m, vector<int> A, vector<int> V) {
        // write your code here
        int n = A.size(), i, j;
        vector<int> res(m + 1);
        
        for(i = 0; i < n; ++i)
        {
            for(j = m; j >= A[i]; --j)
            {
                res[j] = max(res[j - A[i]] + V[i], res[j]);
            }
        }
        
        return res[m];
    }
};




3、Backpack VI


题目描述:

Given an integer array nums with all positive numbers and no duplicates, find the number of 
possible combinations that add up to a positive integer target.

Example
Given nums = [1, 2, 4], target = 4


The possible combination ways are:
[1, 1, 1, 1]
[1, 1, 2]
[1, 2, 1]
[2, 1, 1]
[2, 2]
[4]

return 6





题目分析:

该问题是 重复选择+不同排列+装满可能性总数

这道题开始就把我给看晕了。。

代码如下:

class Solution {
public:
    /**
     * @param nums an integer array and all positive numbers, no duplicates
     * @param target an integer
     * @return an integer
     */
    int backPackVI(vector<int>& nums, int target) {
        // Write your code here
        int n = nums.size(), i, j;
        vector<int> res(target + 1);
        res[0] = 1;
        
        for(i = 1; i <= target; ++i)
        {
            for(j = 0; j < n; ++j)
            {
                if(nums[j] <= i)
                {
                    res[i] += res[i - nums[j]];
                }
            }
        }
        
        return res[target];
    }
};


后来看到一个比较好的解释:

假设用 res[i] 表示容量为 i 的时候,有多少种装的方法。

每次考虑把当前 nums[j] 的空间腾出来,然后装进去 nums[j] 。这样的话需要考虑 res[i - nums[j]] 有多少种方法,把这些方法加到当前的 res[i] 中去。

初始化res[0] = 1,因为假如 res[2] = 0,第一件物品 nums[0] = 2,那么根据上面所说,res[2] += res[0],现在 res[2] 应该为2,因为

把第一件物品 2 放进去空包里也算一种方法。

1
0
查看评论
发表评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场

0-1背包问题入门小结 动态规划(DP)经典题目 POJ324 POJ1276

最近在做背包问题,今天写点东西总结一下。         背包问题,常见的有三种类型:基本的0-1背包、完全背包和多重背包、二维背包        ...
  • sj13051180
  • sj13051180
  • 2011-08-15 10:42
  • 24061

背包问题---01背包(原理,伪代码,编程实现)

01背包是在M件物品取出若干件放在空间为W的背包里,每件物品的体积为C1,C2,…,Cn,与之相对应的价值为W1,W2,…,Wn.求解将那些物品装入背包可使总价值最大。         动态规划...
  • u011644423
  • u011644423
  • 2014-07-17 10:57
  • 3771

dd大牛的背包九讲-背包问题汇总

背包九讲 目录  第一讲 01背包问题  第二讲 完全背包问题  第三讲 多重背包问题  第四讲 混合三种背包问题  第五讲 二维费用的背包问题  第六讲 分组的背包问题  第七讲 有依赖的背包...
  • stack_queue
  • stack_queue
  • 2016-12-09 20:59
  • 5890

DP背包问题小结(01背包,完全背包,需恰好装满或不需,一维DP、二维DP)

1) 01背包基础,求背包所装物品价值之和的最大值,不要求恰好装满时,采用易于理解的二维DP数组存储。 #include #include using namespace std; int d...
  • a272846945
  • a272846945
  • 2016-03-17 22:53
  • 2125

01背包问题(动态规划入门)

01背包问题 给定N种物品和一个背包。物品i的重量是Wi,其价值位Vi ,背包的容量为M。问应该如何选择装入背包的物品,使得转入背包的物品的总价值为最大?? 在选择物品的时候,对每种物品i只有两...
  • yfainaer
  • yfainaer
  • 2017-03-29 17:58
  • 433

最通俗易懂的01背包问题讲解

1、动态规划(DP)  动态规划(Dynamic Programming,DP)与分治区别在于划分的子问题是有重叠的,解过程中对于重叠的部分只要求解一次,记录下结果,其他子问题直接使用即可,减少了重复...
  • FX677588
  • FX677588
  • 2017-04-02 14:02
  • 1394

0-1背包问题的四种写法

本节回顾0-1背包的基本模型,关于它的实现有很多种写法,这里对不同实现做个简单列举,主要是写代码练手了,主要有以下几方面内容: ==0-1背包问题定义 & 基本实现 ==0-1背包使用滚动数组...
  • txl16211
  • txl16211
  • 2015-05-20 13:21
  • 8103

背包问题:动态规划和贪心算法

以下关于动态规划的文字描述来源1. 动态规划 作者:Hawstein 出处:http://hawstein.com/posts/dp-knapsack.html 一切都要从一则故事说起。话说...
  • quzhongxin
  • quzhongxin
  • 2015-06-26 21:01
  • 3931

从动态规划方面(dp)讨论背包问题

首先,看一个基础的问题: 【物品无限的背包问题】: n种物品,每种均有无穷个,第i种物品的体积为v[i],价值为w[i],装C容量的背包,不超过C的情况下价值最多为? 输入: 3 3 ...
  • ITermeng
  • ITermeng
  • 2016-02-27 22:46
  • 994

01背包问题(DP解决)

问题主题:著名的01背包问题 问题描述: 有n个重量和价值分别为wi、vi的物品,现在要从这些物品中选出总重量不超过W的物品,求所有挑选方案中的价值最大值。 限制条件: 1<=N<=100 ...
  • qq_31828929
  • qq_31828929
  • 2016-09-03 21:07
  • 552
    个人资料
    • 访问:99215次
    • 积分:2199
    • 等级:
    • 排名:第19849名
    • 原创:121篇
    • 转载:7篇
    • 译文:0篇
    • 评论:19条
    博客专栏
    最新评论