0-1背包问题及python实现

0-1背包问题及python实现

1. 问题提出

​ 0-1背包问题是动态规划中入门的经典题型,掌握0-1背包问题背后的本质有助于更好地理解动态规划问题,话不多说,首先来看看0-1背包问题究竟是什么吧~

问题描述:设有n件物品x1, x2, …, xn,每件物品有一个价值和一个重量,分别记为v1, v2, …, vn和w1, w2, …, wn,其中所有的wi均为整数。现有一个背包,其最大载重量为m,要求从这n件物品中任取若干件(这些物品要么被装入要么被留下)。问背包中装入哪些物品可是得所装物品的价值和最大?

样例1输入:m=11, n=6

物体编号i012345
物体重量wi2456103
物体价值vi1745111

样例1输出:12

2. 递归实现暴力枚举

​ 在不考虑时间的情况下,暴力枚举是一个很直观的方法,也就是枚举所有可能装入背包的物品组合,找出价值和最大的组合,只不过这里我们是用递归的方式来实现这个枚举。

​ 我们首先定义递归函数backpack(i, w),它表示在0至i号物品中,载重量为w时的所有可行方案的最大价值和,那么很容易得到递归关系式:backpack(i, w) = max{backpack(i - 1, w - wi) + vi, backpack(i - 1, w)},也就是在0至i号物品中,载重量为w的最大价值和等于0至i-1号物品中,载重量为w-wi的最大价值和加上vi的值0至i-1号物品中,载重量为w的最大价值和中的较大值,python代码如下。

def backpack(i, w):
    global weight, value
    if w < 0:
        return -float("inf")
    if i < 0:
        return 0
    return max(backpack(i-1, w-weight[i])+value[i], backpack(i-1, w))


m, n = 11, 6
weight = [2, 4, 5, 6, 10, 3]
value = [1, 7, 4, 5, 11, 1]
print(backpack(n-1, m))

3. 带备忘递归

​ 很显然,暴力枚举的复杂度是十分高的,那么我们可以如何去优化这个暴力枚举的复杂度呢?我们从一种简单的特殊情况来分析,假设0至n号物品的重量均为c,那么我们可以得到以下的递归树。

在这里插入图片描述

​ 很显然,在经过2层递归后,就出现了重复的子问题backpack(n-2, w-c),并且继续递归下去,重复的子问题会越来越多。因此,我们可以构造一个备忘录,对于重复求解的问题,计算一次后记入备忘录,下次遇到相同的问题,只需要从备忘录中查询就行。python代码也只需要在原先的基础上做简单的变动即可。

def backpack(i, w):
    global weight, value, p
    if w < 0:
        return -float("inf")
    if i < 0:
        return 0
    if p[i][w]:
        return p[i][w]
    p[i][w] = max(backpack(i-1, w-weight[i])+value[i], backpack(i-1, w))
    return p[i][w]


m, n = 11, 6
weight = [2, 4, 5, 6, 10, 3]
value = [1, 7, 4, 5, 11, 1]
p = [[None for j in range(m + 1)] for i in range(n)]
print(backpack(n - 1, m))

4. 动态规划

​ 因为递归毕竟是递归,它的计算过程肯定首先是自顶向下递归到最原始的单一问题,之后再通过自底向上的递推过程,计算每一步的解,最终回归到了最初的问题。那么我是否们能够通过这个备忘录的数组直接自底向上计算呢?

​ 答案当然是可以的,那就是动态规划。我们可以通过递推关系公式p[i, w]=max{p[i-1, w-weight[i]]+value[i], p[i-1, w]}来逐行逐行的填充这个数组,最终数组最右下角的值就是最大价值和。

p[i, w]01234567891011
0001111111111
1001177888888
2001177888111112
3001177888111212
4001177888111212
5001177888111212

​ 现在我们已经得到了最大价值,还有一个问题,那就是到底选择了哪些物品能够获得这个最大价值?我们可以用rec数组记录下决策过程,在填充p[i, j]时,如果选择了i商品,则rec[i, j]=1;反之,rec[i, j]=0。然后我们可以用rec数组来追踪最优解,一行一行往回倒推,当rec[i, j]=1时,选择商品i,考察子问题p[i-1, w-weight[i]];当rec[i, j]=0时,不选商品i,考察子问题p[i-1, c]。

​ 以样例1为例,rec[5, 11]=0,说明没有选择5号商品,则rec[5, 11]是由rec[4, 11]推导而来的;再看rec[4, 11]=0,说明也没有选择4号商品,则rec[4, 11]是由rec[3, 11]推导而来的;再看rec[3, 11]=0,说明也没有选择3号商品,则rec[3, 11]是由rec[2, 11]推导而来的;再看rec[2, 11]=1,说明选择了2号商品,则rec[2, 11]是由rec[1, 11-weight[2]]即rec[1, 6]推导而来的;再看rec[1, 6]=1,说明也选择了1号商品,则rec[1, 6]是由rec[0, 6-weight[1]]即rec[0, 2]推导而来的;再看rec[0, 2]=1,说明也选择了0号商品。

rec[i, w]01234567891011
0001111111111
1000011111111
2000000000111
3000000000010
4000000000000
5000000000000

​ 于是,python的代码也就比较容易得到了。

w, n = 11, 6
weight = [2, 4, 5, 6, 10, 3]
value = [1, 7, 4, 5, 11, 1]
p = [[0 for j in range(w + 1)] for i in range(n)]
rec = [[0 for j in range(w + 1)] for i in range(n)]
for j in range(w + 1):
    if weight[0] <= j:
        p[0][j] = value[0]
        rec[0][j] = 1
for i in range(1, n):
    for j in range(w + 1):
        if weight[i] <= j and value[i] + p[i-1][j-weight[i]] > p[i-1][j]:
            p[i][j] = value[i] + p[i-1][j-weight[i]]
            rec[i][j] = 1
        else:
            p[i][j] = p[i-1][j]
print(p[n-1][w])
print("choose item ", end="")
tmp = w
for i in range(n-1, -1, -1):
    if rec[i][tmp] == 1:
        print(i, end=" ")
        tmp -= weight[i]

5. 总结

​ 动态规划算法的本质就是把原始问题分解成子问题,并寻找小问题和大问题之间的关系,从小问题向大问题的规模去扩展,最后达到最优解。总的来说,动态规划算法可以分解为4个步骤:

  • 问题结构分析:描述一个最优解的结构;
  • 递推关系建立:递推地定义最优解的值;
  • 自底向上计算:以“自底向上”的方式计算最优解的值;
  • 最优方案追踪:从已计算的信息中构建出最优解的路径。

​ 在本题中,问题结构分析就是给出0-1背包问题的表现形式,也就是p[n-1, w]表示在0至n-1号物品中,载重量为w时的所有可行方案的最大价值和;递推关系建立需要去分析最优子结构(最优子结构性质就是指问题的最优解由相关子问题最优解组合而成,子问题可以独立求解),在本题中,是否取第i件商品就构成了原问题的两个相关子问题,然后两个相关子问题的最优解的较大值也就是原问题的最优解,于是递推关系也就得到了,p[i, w]=max{p[i-1, w-weight[i]]+value[i], p[i-1, w]};再得到递推关系后,自底向上计算就很容易了,只要在初始化后,根据递推关系公式就可以依次求解问题了;如果需要输出最优方案,那么就需要使用rec数组来追踪最优方案,rec数组记录了决策的过程。

  • 9
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
【资源说明】 分别使用贪心算法、蛮力法、动态规划法解决分数背包问题和0-1背包问题python源码(带注释).zip分别使用贪心算法、蛮力法、动态规划法解决分数背包问题和0-1背包问题python源码(带注释).zip分别使用贪心算法、蛮力法、动态规划法解决分数背包问题和0-1背包问题python源码(带注释).zip分别使用贪心算法、蛮力法、动态规划法解决分数背包问题和0-1背包问题python源码(带注释).zip分别使用贪心算法、蛮力法、动态规划法解决分数背包问题和0-1背包问题python源码(带注释).zip分别使用贪心算法、蛮力法、动态规划法解决分数背包问题和0-1背包问题python源码(带注释).zip 分别使用贪心算法、蛮力法、动态规划法解决分数背包问题和0-1背包问题python源码(带注释).zip 分别使用贪心算法、蛮力法、动态规划法解决分数背包问题和0-1背包问题python源码(带注释).zip 【备注】 1、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用!有问题请及时沟通交流。 2、适用人群:计算机相关专业(如计科、信息安全、数据科学与大数据技术、人工智能、通信、物联网、自动化、电子信息等)在校学生、专业老师或者企业员工下载使用。 3、用途:项目具有较高的学习借鉴价值,不仅适用于小白学习入门进阶。也可作为毕设项目、课程设计、大作业、初期项目立项演示等。 4、如果基础还行,或热爱钻研,亦可在此项目代码基础上进行修改添加,实现其他不同功能。 欢迎下载,沟通交流,互相学习,共同进步!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值