01背包问题

简介: 背包问题是在限定背包重量下,选择物品使总价值最大。常见的类型包括0-1背包、部分背包、完全背包和多重背包。本文通过动态规划算法解决0-1背包问题,以一个携带11斤背包、5件商品的场景为例,详细讲解如何通过表格逐步计算最大收益。最终通过伪代码总结了动态规划的实现过程。

商店的货架上摆放着不同重量和价值的商品,一个小偷在商店行窃,他携带的背包只能装固定重量的商品。装哪些商品才能获得最大的收益呢?在限定条件内找到最佳的物品组合,这样的问题统称为背包问题。


根据限定的条件不同,背包问题还可以细分:

  • 部分背包问题:所有物品是可再分的,即允许将某件物品的一部分(例如 1/3)放入背包;
  • 0-1 背包问题:所有物品不可再分,要么整个装入背包,要么放弃,不允许出现“仅选择物品的 1/3 装入背包”的情况;
  • 完全背包问题:不对每一件物品的数量做限制,同一件物品可以选择多个装入背包;
  • 多重背包问题:每件物品的数量是有严格规定的,比如物品 A 有 2 件,物品 B 有 3 件。


前面章节中,我们学会了用贪心算法解决部分背包问题。本节,我们学习如何用动态规划算法解决 0-1 背包问题。

动态规划解决01背包问题

虚拟一个场景,商店中拥有 5 件商品,它们各自的重量和收益分别是:

  • 商品 1:重量 1 斤,收益 1 元;
  • 商品 2:重量 2 斤,收益 6 元;
  • 商品 3:重量 5 斤,收益 18 元;
  • 商品 4:重量 6 斤,收益 22 元;
  • 商品 5:重量 7 斤,收益 28 元。


所有商品不可再分,顾客要么“整件”购买商品,要么放弃购买。一个小偷想窃取商品,他的背包只能装 11 斤商品,如何选择商品才能获得最大的收益呢?


动态规划算法解决此问题的核心思想是:背包承重 1 斤时所能获得的最大收益是很容易计算的,在此基础上,可以推算出背包承重 2 斤、3斤、...、14斤、15斤时所能获得的最大收益。建立如下这张表格,依次将各个商品装入不同承重的背包中,计算出它们所能获得的最大收益。


表1:动态规划算法解决01背包问题

商品种类 背包承重
0 1 2 3 4 5 6 7 8 9 10 11
不装任何商品 0 0 0 0 0 0 0 0 0 0 0 0
w1 = 1,v1 = 1                        
w2 = 2,v2 = 6                        
w3 = 5,v3 = 18                        
w4 = 6,v4 = 22                        
w5 = 7,v5 = 28                        

表格中,wi 表示第 i 件商品的重量,vi 表示第 i 件商品的收益值。承重不同的各个背包尚未装入商品时,对应的收益值都为 0。

1) 首先考虑将商品一装入各个背包,除了承重值为 0 的背包,其它背包都能装入,且与不装任何商品相比,装入商品一后各个背包的收益更大,各个背包的收益值如表 2 所示:


表2:动态规划算法解决01背包问题

商品种类 背包承重
0 1 2 3 4 5 6 7 8 9 10 11
不装任何商品 0 0 0 0 0 0 0 0 0 0 0 0
w1 = 1,v1 = 1 0 1 1 1 1 1 1 1 1 1 1 1
w2 = 2,v2 = 6                        
w3 = 5,v3 = 18                        
w4 = 6,v4 = 22                        
w5 = 7,v5 = 28                        


我们用 f(n) 表示承重值为 n 的背包对应的最大收益。从算法的角度,各个背包收益值是这样计算的:f(1)=1+f(0)、f(2)=1+f(1)、...、f(11)=1+f(10),其中等号右侧表达式中的 1 指的是商品一的收益值,f(0)~f(10) 指的是不装任何商品时承重分别为 0~10 的背包对应的收益值,借助表格可以看到,它们的值都为 0。


2) 考虑将商品二装入各个背包,除了承重值为 0 和 1 的背包,其它背包都可以装入。我们可以计算出它们各自对应的收益值:

f(2) = 6 + f(0) = 6

f(3) = 6 + f(1) = 7

f(4) = 6 + f(2) = 7

...

f(9) = 6 + f(7) = 7

f(10) = 6 + f(8) = 7

f(11) = 6 + f(9) = 7

等号右侧 f(0)~f(9) 的值是表 2 中装入商品一的各个背包对应的收益值。相比装入商品一统计的各个背包的收益值,装入商品二能使提高各个背包的收益。更新后的表格为:


表3:动态规划算法解决01背包问题

商品种类 背包承重
0 1 2 3 4 5 6 7 8 9 10 11
不装任何商品 0 0 0 0 0 0 0 0 0 0 0 0
w1 = 1,v1 = 1 0 1 1 1 1 1 1 1 1 1 1 1
w2 = 2,v2 = 6 0 1 6 7 7 7 7 7 7 7 7 7
w3 = 5,v3 = 18                        
w4 = 6,v4 = 22                        
w5 = 7,v5 = 28                        


3) 考虑将商品三装入各个背包,除了承重值为小于 5 的背包,其它背包都可以装入。我们可以计算出它们各自对应的收益值:

f(5) = 18 + f(0)  = 18

f(6) = 18 + f(1) = 19

f(7) = 18 + f(2) = 24

f(8) = 18 + f(3) = 25

f(9) = 18 + f(4) = 25

f(10) = 18 + f(5) = 25

f(11) = 18 + f(6) = 25

等号右侧 f(0)~f(6) 的值是表 2 中装入商品二的各个背包对应的收益值。和装入商品二时统计的各个背包的收益值相比,装入商品三能提高各个背包的收益。更新后的表格为:


表4:动态规划算法解决01背包问题

商品种类 背包承重
0 1 2 3 4 5 6 7 8 9 10 11
不装任何商品 0 0 0 0 0 0 0 0 0 0 0 0
w1 = 1,v1 = 1 0 1 1 1 1 1 1 1 1 1 1 1
w2 = 2,v2 = 6 0 1 6 7 7 7 7 7 7 7 7 7
w3 = 5,v3 = 18 0 1 6 7 7 18 19 24 25 25 25 25
w4 = 6,v4 = 22                        
w5 = 7,v5 = 28                        


4) 采用同样的方法,我们可以将表 4 中缺失的数据补全,最终得到的表格为:


表5:动态规划算法解决01背包问题

商品种类 背包承重
0 1 2 3 4 5 6 7 8 9 10 11
不装任何商品 0 0 0 0 0 0 0 0 0 0 0 0
w1 = 1,v1 = 1 0 1 1 1 1 1 1 1 1 1 1 1
w2 = 2,v2 = 6 0 1 6 7 7 7 7 7 7 7 7 7
w3 = 5,v3 = 18 0 1 6 7 7 18 19 24 25 25 25 25
w4 = 6,v4 = 22 0 1 6 7 7 18 22 24 28 29 29 40
w5 = 7,v5 = 28 0 1 6 7 7 18 22 28 29 34 35 40


注意,并不是每试图装入一个新商品,背包的收益一定会提高。举个例子,承重为 7 斤的背包装入商品四时的最大收益是:f(7) = 22+f(1) = 23,装入商品三时最大的收益值为:f(7) = 18+f(2) = 24。因此,表 5 中承重 7 斤的背包装入商品 4 时对应的收益值仍为 24,并未发生改变。


结合表 5,当背包承重为 11 斤时,所能获得的最大收益为 40 元。如下以伪代码的形式给大家总结了以上推理的整个过程:

输入 N    // 指定商品种类

输入 W    // 指定背包载重量

//w[] 记录各个商品的载重量,v[] 记录各个商品对应的收益

knapsack01(w[] , v[]):

   //逐个遍历每个商品

   for i <- 1 to N:

       //求出从 1 到 W 各个载重量对应的最大收益

       for j <- 1 to W:

           //如果背包载重量小于商品总重量,则商品无法放入背包,收益不变

           if  j < w[i]:

               result[i][j] = result[i-1][j]

           else:

               //比较装入该商品和不装该商品,哪种情况获得的收益更大,记录最大收益值

               result[i][j] = max(result[i-1][j] , v[i]+result[i-1][j-w[i]])

   return result

相关文章
|
4月前
|
算法 Java C语言
弗洛伊德算法求最短路径
弗洛伊德算法用于寻找加权图中各顶点间的最短路径,适用于无向图和有向图。算法基于动态规划思想,通过枚举中间顶点来更新路径,确保最终得到最短路径。该算法要求路径权值非负,否则可能出错。
446 0
|
4月前
|
存储 搜索推荐 算法
归并排序算法
归并排序是一种基于分治思想的高效排序算法,通过将序列不断划分为不可再分的子序列,再两两合并完成排序。其时间复杂度为O(nlogn),适用于对序列进行升序或降序排列。
320 0
|
4月前
|
存储 算法 Java
求数组中的最大值和最小值
本文介绍了在程序中如何查找数组中的最大值和最小值,重点讲解了两种算法:普通算法和分治算法。普通算法通过遍历数组直接比较元素大小,找出最值;而分治算法则通过递归将数组划分成更小的部分,分别找出各部分的最大值,最终合并结果得到整个数组的最大值。文章以 {3,7,2,1} 为例,详细演示了两种算法的实现过程,并提供了 C、Java 和 Python 的代码示例。
302 0
|
4月前
|
算法 Java C语言
汉诺塔问题
汉诺塔问题源自印度古老传说,涉及将一组圆盘从一根柱子移动到另一根,遵循特定规则。文章详细介绍了该问题的背景、解决思路以及如何使用分治算法实现,同时提供了C语言、Java和Python的代码示例。
303 0
|
4月前
|
存储 算法 Java
寻找图中是否存在路径
本节介绍了如何使用并查集判断图中两个顶点之间是否存在有效路径。通过将图的边信息存储到并查集中,同一连通区域的顶点会处于同一集合。只需比较两个顶点的根节点是否相同,即可判断它们是否连通。文中提供了C语言、Java和Python的实现代码,并通过示例验证了算法的正确性。
129 0
|
4月前
|
存储 算法
最小生成树的概念与思想
数据结构中的图存储结构包含顶点和边,分为连通图与非连通图。生成树是包含所有顶点、任意两点间仅有一条通路的极小连通图。最小生成树则是权值总和最小的生成树,常用于解决道路建设等实际问题,常用算法有普里姆算法和克鲁斯卡尔算法。
118 0
|
4月前
|
算法 程序员
时间复杂度和空间复杂度的概念
本文介绍了如何评估算法的执行效率和内存占用,重点讲解了时间复杂度和空间复杂度的概念及其计算方法。通过大O记法,可以量化算法的运行时间和内存使用情况,从而在不同算法间做出合理选择。
149 0
|
4月前
|
算法 Java 定位技术
迷宫问题
迷宫问题是指在给定区域内寻找从起点到终点的可行路径。可以使用回溯算法解决,通过不断尝试四个方向(上下左右)移动,若无法前进则回退,直到找到终点或遍历所有可能路径。文中还给出了C语言、Java和Python的实现代码,并展示了运行结果。
182 0
|
4月前
|
存储 算法 Java
组合问题
组合问题是从给定的N个数中找出任意K个数的所有组合。由于组合内元素无顺序,因此[1,2]和[2,1]视为同一组合。解决组合问题常用的方法包括嵌套循环和回溯算法。嵌套循环适用于K值较小的情况,而回溯算法更适合K值较大的情况,能有效避免多层循环带来的代码复杂性和低效率问题。回溯算法通过递归实现,能动态选择元素并逐步构建组合,最终输出所有符合条件的组合结果。
178 0