1、简介

假设我们有n件物品,分别编号为1, 2...n。其中编号为i的物品价值为vi,它的重量为wi。为了简化问题,假定价值和重量都是整数值。现在,假设我们有一个背包,它能够承载的重量是W。现在,我们希望往包里装这些物品,使得包里装的物品价值最大化,那么我们该如何来选择装的东西呢?问题结构如下图所示:

这个问题其实根据不同的情况可以归结为不同的解决方法。假定我们这里选取的物品每个都是独立的,不能选取部分。也就是说我们要么选取某个物品,要么不能选取,不能只选取一个物品的一部分。这种情况,我们称之为0-1背包问题。而如果我们可以使用部分的物品的话,这个问题则成为部分背包(fractional knapsack)问题。这里我们只考虑0-1背包问题。

2、初步分析

对于这个问题,一开始确实有点不太好入手。一堆的物品,每一个都有一定的质量和价值,我们能够装入的总重量有限制,该怎么来装使得价值最大呢?对于这n个物品,每个物品我们可能会选,也可能不选,那么我们总共就可能有2^n种组合选择方式。如果我们采用这种办法来硬算的话,则整体的时间复杂度就达到指数级别的,肯定不可行。

现在我们换一种思路。既然每一种物品都有价格和重量,我们优先挑选那些单位价格最高的是否可行呢?比如在下图中,我们有3种物品,他们的重量和价格分别是10, 20, 30 kg和60, 100, 120。

那么按照单位价格来算的话,我们最先应该挑选的是价格为60的元素,选择它之后,背包还剩下50 - 10 = 40kg。再继续前面的选择,我们应该挑选价格为100的元素,这样背包里的总价值为60 + 100 = 160。所占用的重量为30, 剩下20kg。因为后面需要挑选的物品为30kg已经超出背包的容量了。我们按照这种思路能选择到的最多就是前面两个物品。如下图:

按照我们前面的期望,这样选择得到的价值应该是最大的。可是由于有一个背包重量的限制,这里只用了30kg,还有剩下20kg浪费了。这会是最优的选择吗?我们看看所有的选择情况:

很遗憾,在这几种选择情况中,我们前面的选择反而是带来价值最低的。而选择重量分别为20kg和30kg的物品带来了最大的价值。看来,我们刚才这种选择最佳单位价格的方式也行不通。

3、动态规划思路

既然前面两种办法都不可行,我们再来看看有没有别的方法。我们再来看这个问题。我们需要选择n个元素中的若干个来形成最优解,假定为k个。那么对于这k个元素a1, a2, ...ak来说,它们组成的物品组合必然满足总重量<=背包重量限制,而且它们的价值必然是最大的。因为它们是我们假定的最优选择嘛,肯定价值应该是最大的。假定ak是我们按照前面顺序放入的最后一个物品。它的重量为wk,它的价值为vk。既然我们前面选择的这k个元素构成了最优选择,如果我们把这个ak物品拿走,对应于k-1个物品来说,它们所涵盖的重量范围为0-(W-wk)。假定W为背包允许承重的量。假定最终的价值是V,剩下的物品所构成的价值为V-vk。这剩下的k-1个元素是不是构成了一个这种W-wk的最优解呢?

我们可以用反证法来推导。假定拿走ak这个物品后,剩下的这些物品没有构成W-wk重量范围的最佳价值选择。那么我们肯定有另外k-1个元素,他们在W-wk重量范围内构成的价值更大。如果这样的话,我们用这k-1个物品再加上第k个,他们构成的最终W重量范围内的价值就是最优的。这岂不是和我们前面假设的k个元素构成最佳矛盾了吗?所以我们可以肯定,在这k个元素里拿掉最后那个元素,前面剩下的元素依然构成一个最佳解。

现在我们经过前面的推理已经得到了一个基本的递推关系,就是一个最优解的子解集也是最优的。可是,我们该怎么来求得这个最优解呢?我们这样来看。假定我们定义一个函数c[i, w]表示到第i个元素为止,在限制总重量为w的情况下我们所能选择到的最优解。那么这个最优解要么包含有i这个物品,要么不包含,肯定是这两种情况中的一种。如果我们选择了第i个物品,那么实际上这个最优解是c[i - 1, w-wi] + vi。而如果我们没有选择第i个物品,这个最优解是c[i-1, w]。这样,实际上对于到底要不要取第i个物品,我们只要比较这两种情况,哪个的结果值更大不就是最优的么?

在前面讨论的关系里,还有一个情况我们需要考虑的就是,我们这个最优解是基于选择物品i时总重量还是在w范围内的,如果超出了呢?我们肯定不能选择它,这就和c[i-1, w]一样。

这里有一点值得注意,这里的wi指的是第i个物品的重量,而不是到第i个物品时的总重量。

另外,对于初始的情况呢?很明显c[0, w]里不管w是多少,肯定为0。因为它表示我们一个物品都不选择的情况。c[i, 0]也一样,当我们总重量限制为0时,肯定价值为0。

这样,基于我们前面讨论的这3个部分,我们可以得到一个如下的递推公式:

有了这个关系,我们可以更进一步的来考虑代码实现了。我们有这么一个递归的关系,其中,后面的函数结果其实是依赖于前面的结果的。我们只要按照前面求出来最基础的最优条件,然后往后面一步步递推,就可以找到结果了。

我们再来考虑一下具体实现的细节。这一组物品分别有价值和重量,我们可以定义两个数组int[] v, int[] w。v[i]表示第i个物品的价值,w[i]表示第i个物品的重量。为了表示c[i, w],我们可以使用一个int[i][w]的矩阵。其中i的最大值为物品的数量,而w表示最大的重量限制。按照前面的递推关系,c[i][0]和c[0][w]都是0。而我们所要求的最终结果是c[n][w]。所以我们实际中创建的矩阵是(n + 1) x (w + 1)的规格。

4、Python代码实现

import numpy as np

def solve(vlist,wlist,totalWeight,totalLength):

resArr = np.zeros((totalLength+1,totalWeight+1),dtype=np.int32)

for i in range(1,totalLength+1):

for j in range(1,totalWeight+1):

if wlist[i] <= j:

resArr[i,j] = max(resArr[i-1,j-wlist[i]]+vlist[i],resArr[i-1,j])

else:

resArr[i,j] = resArr[i-1,j]

return resArr[-1,-1]

if __name__ == '__main__':

v = [0,60,100,120]

w = [0,10,20,30]

weight = 50

n = 3

result = solve(v,w,weight,n)

print(result)

5、复杂度优化

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

先考虑上面讲的基本思路如何实现,肯定是有一个主循环 i=1..N,每次算出来 二维数组 f[i][0..W]的所有值。那么,如果只用一个数组 f[0..W],能不能保证 第 i 次循环结束后 f[w]中表示的就是我们定义的状态 f[i][w]呢?f[i][w]是由 f[i-1][w]和 f[i-1][w-c[i]]两个子问题递推而来,能否保证在推 f[i][w]时(也 即在第 i 次主循环中推 f[w]时)能够得到 f[i-1][w]和 f[i-1][w-w[i]]的值呢? 事实上,这要求在每次主循环中我们以 v=V..0 的顺序推 f[w],这样才能保证推 f[v]时 f[v-w[i]]保存的是状态 f[i-1][w-w[i]]的值。改进后的代码如下:

def solve2(vlist,wlist,totalWeight,totalLength):

resArr = np.zeros((totalWeight)+1,dtype=np.int32)

for i in range(1,totalLength+1):

for j in range(totalWeight,0,-1):

if wlist[i] <= j:

resArr[j] = max(resArr[j],resArr[j-wlist[i]]+vlist[i])

return resArr[-1]

if __name__ == '__main__':

v = [0,60,100,120]

w = [0,10,20,30]

weight = 50

n = 3

result = solve2(v,w,weight,n)

print(result)

6、进一步思考

我们看到的求最优解的背包问题题目中,事实上有两种不太相同的问法。有的题 目要求“恰好装满背包”时的最优解,有的题目则并没有要求必须把背包装满。 一种区别这两种问法的实现方法是在初始化的时候有所不同。

如果是第一种问法,要求恰好装满背包,那么在初始化时除了 f[0]为 0 其它 f[1..W]均设为-∞,这样就可以保证最终得到的 f[N]是一种恰好装满背包的最 优解。

如果并没有要求必须把背包装满,而是只希望价格尽量大,初始化时应该将 f[0..W]全部设为 0。

为什么呢?可以这样理解:初始化的 f 数组事实上就是在没有任何物品可以放入 背包时的合法状态。如果要求背包恰好装满,那么此时只有容量为 0 的背包可能 被价值为 0 的 nothing“恰好装满”,其它容量的背包均没有合法的解,属于未 定义的状态,它们的值就都应该是-∞了。如果背包并非必须被装满,那么任何 容量的背包都有一个合法解“什么都不装”,这个解的价值为 0,所以初始时状 态的值也就全部为 0 了。

这个小技巧完全可以推广到其它类型的背包问题,后面也就不再对进行状态转移 之前的初始化进行讲解。

7、总结

01 背包问题是最基本的背包问题,它包含了背包问题中设计状态、方程的最基 本思想,另外,别的类型的背包问题往往也可以转换成 01 背包问题求解。故一 定要仔细体会上面基本思路的得出方法,状态转移方程的意义,以及最后怎样优 化的空间复杂度

01背包python解法_0-1背包问题及Python代码实现相关推荐

  1. 算法实验 01背包 暴力解法 java实现

    01背包 暴力解法 01背包问题正如其名,其本质就是真和假,0和1.每个物品只有要么被装进背包,要么没有装进背包这两种状态.其暴力解法也算是一种全排列问题. 如上图所示,我们可以用一个数组used来表 ...

  2. NOJ--宠物小精灵之收服(01背包,二维费用背包问题)

    宠物小精灵之收服 1000ms  65536K Description: 宠物小精灵是一部讲述小智和他的搭档皮卡丘一起冒险的故事. 一天,小智和皮卡丘来到了小精灵狩猎场,里面有很多珍贵的野生宠物小精灵 ...

  3. 约瑟夫环问题python解法_约瑟夫环问题python解法 | 学步园

    约瑟夫环问题:已知n个人(以编号1,2,3...n分别表示)围坐在一张圆桌周围.从编号为k的人开始报数,数到k的那个人被杀掉:他的下一个人又从1开始报数,数到k的那个人又被杀掉:依此规律重复下去,直到 ...

  4. 约瑟夫环问题python解法_约瑟夫环问题python解法

    约瑟夫环问题:已知n个人(以编号1,2,3-n分别表示)围坐在一张圆桌周围.从编号为k的人开始报数,数到k的那个人被杀掉:他的下一个人又从1开始报数,数到k的那个人又被杀掉:依此规律重复下去,直到圆桌 ...

  5. 背包问题教程-01背包,完全背包,多重背包,混合背包 收藏

    P01: 01背包问题 题目 有N件物品和一个容量为V的背包.第i件物品的费用是c[i],价值是w[i].求解将哪些物品装入背包可使价值总和最大. 基本思路 这是最基础的背包问题,特点是:每种物品仅有 ...

  6. 动态规划 —— 背包问题 P01 —— 0-1背包

    [概述] 0-1背包问题是最基本的背包问题,它包含了背包问题中设计状态.方程的最基本思想,另外,别的类型的背包问题往往也可以转换成0-1背包问题求解. 特点:每种物品仅有一件,可以选择放或不放. [题 ...

  7. 动态规划 背包问题小结 0-1背包(采药 九度第101题) 完全背包(Piggy-Bank POJ 1384) 多重背包(珍惜现在,感恩生活 九度第103题)

    本小结介绍0-1背包.完全背包以及多重背包问题 记忆要点: 0-1背包:二维数组情况下,顺序遍历体积或者倒序均可以                降维情况下需倒序遍历体积 完全背包:数组降维+顺序遍历 ...

  8. C++背包问题——01背包

    01背包问题 1.题目 2.基本思路 3.优化空间复杂度 4.初始化的细节问题 5.小结 6.代码 1.题目 有N件物品和一个容量为 V V V的背包.放入第i件物品耗费的空间是 C i Ci Ci, ...

  9. ZCMU 1919: kirito's 星爆气流斩【01背包的二进制优化】

    ZCMU 1919: kirito's 星爆气流斩 Time Limit: 2 Sec  Memory Limit: 128 MB Description 主角kirito是使用世界首款完全潜行游戏& ...

  10. 背包九讲系列1——01背包、完全背包、多重背包

    我在进行一些互联网公司的技术笔试的时候,对于我来说最大的难题莫过于最后的那几道编程题了,这对算法和数据结构有一定程度上的要求,而"动态规划"又是编程题中经常出现的算法类型,并且对于 ...

最新文章

  1. 静态存储区、堆和栈的区别
  2. win8.1出现 called runscript when not marked in progress
  3. php include 和require的区别与转码
  4. 可以写计算机哪些方面的论文,计算机应用基础方面论文题目 计算机应用基础论文题目哪个好...
  5. 在python中设置密码登录_在python中生成密码
  6. 2018年全国多校算法寒假训练营练习比赛(第四场)F:Call to your teacher
  7. 在ubuntu下安装MonoDevelop
  8. inDesign教程,如何创建具有吸引力的边注栏?
  9. 随手记_英语_50大英文经典句/美句
  10. Atitit 代理CGLIB 动态代理 AspectJ静态代理区别
  11. html中字体 楷体_HTML,CSS,font-family:中文字体的英文名称
  12. stm32点亮流水灯(小白的求学之路)
  13. Transform.rotation所见非所得
  14. 『C++』endl、ends和flush的区别
  15. java解析json天气api,使用Postman获取天气接口API(Json格式)
  16. [转摘]如何让你的计算机无线网卡和有线网卡同时使用
  17. 百度智能云 API鉴权总结
  18. ASEMI代理AD8606ACBZ-REEL7原装ADI车规级AD8606ACBZ-REEL7
  19. 关于MacBook蓝牙键盘鼠标耳机音响等设备各种的卡顿问题
  20. 如何用MathType编辑出积分符号

热门文章

  1. java 电子围栏_怎么画电子围栏,并进行电子围栏进出判断?
  2. 李开复给中国大学生的第三封信---成功、自信、快乐
  3. Kotlin 函数式编程(Kotlin Functional Programming)
  4. Invitation Cards
  5. 对全职高手的自然语言处理
  6. java spring源码_剑指Java自研框架,决胜Spring源码
  7. 模电基础讲解03-三极管
  8. 【专题5:硬件设计】 之 【49.运算放大器详解a - 三极管的放大作用和静态工作点】
  9. Vue子组件重新渲染
  10. 兔子数列(斐波拉契数列)javscript的三种写法