来聊聊背包问题思路

本文解析了背包问题的基本概念,重点讲解了01背包问题的思路,通过实例说明如何通过价值与重量对比找到最优解,并提供了核心代码逻辑。适合初学者理解解题技巧。

缘起


有童鞋反映背包问题太难了,看不懂,不理解精髓,希望小编能做个分享!郑重声明,具体的解法大家可以网上去看,本文主要讲解思路!

背包问题定义和种类


先来看看官方定义:背包问题(Knapsack problem)是一种组合优化的NP完全问题。问题可以描述为:给定一组物品,每种物品都有自己的重量和价格,在限定的总重量内,我们如何选择,才能使得物品的总价格最高。问题的名称来源于如何选择最合适的物品放置于给定背包中。相似问题经常出现在商业、组合数学,计算复杂性理论、密码学和应用数学等领域中。也可以将背包问题描述为决定性问题,即在总重量不超过W的前提下,总价值是否能达到V?它是在1978年由Merkle和Hellman提出的。

简单的来说,就是排列组合,寻求最优,线性规划等等!种类大致分为三种:01背包问题,完全背包问题,多重背包问题!后面两种可以看成是第一种的延伸,也可以拆分成第一种问题处理,我们今天重点分析第一种!

01背包问题


算法很多是共通的,就像技术无国界一样,二分树都是拆成子树求解,完全背包和多重背包,都是转化成01背包,把复杂的问题简单化,把大问题拆成多个子问题!

  • 问题描述
  • 问题思路
  • 解题代码

问题描述


n{n}n件物品和1{1}1个容量为W的背包。每种物品均只有一件,第i{i}i件物品的重量为weights[i]{weights[i]}weights[i],价值为values[i]{values[i]}values[i],求解将哪些物品装入背包可使价值总和最大。

问题思路


看到上面的问题是不是有点抽象,有种无从下手的感觉,那么我们来转化下:
有5件物品,重量分别是weights[4,5,2,3,3],价值分别是values[5,4,2,5,1],背包的容量是10,如何放置才能价值最大!
通过上面一步,我们已经把复杂的问题抽象化了,接下来进入分析阶段!
我们确定背包的容量是固定的,需要挨个放置物品,使其价值最大,我们通过心算可以得出上述最大的值是12,即放入第一个,第三个,第四个。
现在我们需要把上面的过程,抽象化的表达出来:

i/j012345678910
000000000000
100000000000
200000000000
300000000000
400000000000
500000000000

这里的i代表weights和values的下标,j代表我们把容量10的背包做拆分,最大放满是10,我们也可以同时放0-9的物品,可以看作是固定容积的时候,物品选择放与不放!

我们先来看第一个物品,也就是i是1的时候

i/j012345678910
000000000000
100005555555
200000000000
300000000000
400000000000
500000000000

上面很好理解,当你的背包容量是0-3的时候,装不下的,那么自然是0,当是4-10的时候,那么就是4

当i是2的时候

i/j012345678910
000000000000
100005555555
200005555599
300000000000
400000000000
500000000000

这个也好理解,如果容量只能存一件物品,那么肯定是存价值大的,两件都能存,那么肯定是都存起来

接下来我们来看i为3的时候

i/j012345678910
000000000000
100005555555
200005555599
300225577799
400000000000
500000000000

同理我们可以得到i是4和i是5的值
i为4时

i/j012345678910
000000000000
100005555555
200005555599
300225577799
4002557710101212
500000000000

i为5时

i/j012345678910
000000000000
100005555555
200005555599
300225577799
4002557710101212
5002557710101212

最后我们得出的结论是12

接下来我们要进行下一步了,归纳,我们发现我们只需要把第i件物品,在背包容量为j时,和j-weights[i]进行对比,价值最大的那个就是此时的最优解,换而言之,V[i, j]之前的格子都是最优解,也就是现在V[i-1,j]和V[i -1, j - weights[i]] + values[i]比较区最大值,如果加了这个i,值大,那么取加后的值,如果加了这个i,值不大于V[i - 1, j],取现在的值V[i -1, j],这就是01背包问题的核心思路!

解题代码


这个也容易,随便搜下一大堆,这里只写核心的一行逻辑

// j < weights[i],i不能放入的时候,取之前的最优解
V[i][j]=V[i-1][j];
// j > weights[i],拿之前的最优解,和现在放入后最优的值比较
V[i][j]=Math.max(V[i-1][j],V[i-1][j-weights[i]]+values[i]);

边界问题


  • 关于0
  • 代码是如何做到后面高价值的i替代前面低价值的i

关于0


0这个问题很简单,就是第0个物品,和j为0时,那么都是价值都是0,主要是为了应对下面的问题,不能省略哦!

代码是如何做到后面高价值的i替代前面低价值的i


我们还是拿weights[4,5,2,3,3]和values[5,4,2,5,1],背包容量10举例。
当我们i是2,j是8和9的时候,我们来分析下,童鞋们可以看前面的表格。

i是2,j是8的时候,因为不能同时放下i1和i2,V[2, 8]就是V[1, 8]和V[1,3] + 4,,通过之前的每个单元格都是最优解,我们可以知道这就是5和0+4比较,留下大的一个解。显然i2比i1小,所以i1没有被替换,但是如果i2大于i1,那么在这个V[2, 8]单元格,就是i2的值替换了i1,i>2的时候也一样,都是i - 1的解和放入i后背包的最大值比较,获得最优解!

同样,i是2,j是9的时候,V[2, 9]就是V[1, 9]和V[1,4] + 4比较,这也很好的回答了为啥必须有0,是为了计算正好放满这种情况!

总结


  • 每个V[i, j]都是最优解,后面的V[i, j]如果用到前面的,那么就是在背包j当前值确定的情况下,加上i的价值,和之前的V[i - 1,j]比较,留下最大的,这也解释了为何后面i的可能会替代前面的i这个现象!最后V[i, j]最大的那个值就是本题的最优解
  • 完全背包问题和多重背包问题,都可以拆成01背包问题,无非就是多加个循环,这里不赘述了!
  • 01背包问题还有好多优化,滚动数组,末尾遍历,二进制等等,大家自己去看就是了!

尾声


美好的时光总是短暂的,知难行易,你学会了嘛!如果觉得本文对你有用,欢迎转发打赏哦!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值