0/1背包问题 python

 

问题描述

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

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

    

    我们来看这个问题。我们需要选择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]一样。

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

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

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

 

#n个物体的重量(w[0]无用)
w = [0, 2, 2, 6, 5, 4]
#n个物体的价值(p[0]无用)
p = [0, 6, 3, 5, 4, 6]
#计算n的个数
n = len(w) - 1
#背包的载重量
m = 10
#装入背包的物体,元素为True时,对应物体被装入(x[0]无用)
x = [False for raw in range(n + 1)]
v = 0
#optp[i][j]表示在前i个物体中,能够装入载重量为j的背包中的物体的最大价值
optp = [[0 for col in range(m + 1)] for raw in range(n + 1)]
 
def knapsack_dynamic(w, p, n, m, x):
    #计算optp[i][j]
    for i in range(1, n + 1):
        for j in range(1, m + 1):
            optp[i][j] = optp[i - 1][j]
            if (j >= w[i]) and (optp[i - 1][j - w[i]] + p[i] > optp[i - 1][j]):
                optp[i][j] = optp[i - 1][j - w[i]] + p[i]
    
    #递推装入背包的物体
    j = m
    for i in range(n, 0, -1):
        if optp[i][j] > optp[i - 1][j]:
            x[i] = True
            j = j - w[i]  
    
    #返回最大价值
    v = optp[n][m]
    return v
 
print('最大值为:' + str(knapsack_dynamic(w, p, n, m, x)))
print(x[1:])

 

 

 

    此题的核心就在d_k两个循环体里,因为之前已经实现了一个类二维数组,所有dp[ 0 ] [ j ] dp[ i ][ 0 ]都是零,所以从i=1开始,通过循环--递归一层一层的赋值,所有的下一层值都由上一层的值相等决定或者,dp i-1,j-w[ i ]+p[ i ]决定,最后全部铺满,在求最大值的时候,直接取表格中的值即可。 

    这个背包问题困扰了我两天,才坎坎理解了它,在此很感谢下面两篇文章的帮助,本篇文章除了最后的理解也都是引用了它们的文章(懒癌发作,是在是不愿意自己写了......)

    ~本篇文章参考:

    1:http://blog.sina.com.cn/s/blog_3fe961ae0100zap7.html
    2:http://shmilyaw-hotmail-com.iteye.com/blog/2009761

 

 

  • 5
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
蛮力法(也称为穷举法)是一种通过枚举所有可能的解来解决问题的方法。在0/1背包问题中,蛮力法通过尝试所有可能的物品组合来找到最优解。 以下是使用蛮力法求解0/1背包问题Python实现示例: ```python def brute_force_knapsack(weights, values, capacity): num_items = len(weights) max_value = 0 best_combination = [] # 枚举所有可能的物品组合 for i in range(2 ** num_items): current_weight = 0 current_value = 0 current_combination = [] # 将二进制表示的组合转换为物品组合 for j in range(num_items): if (i >> j) & 1: current_weight += weights[j] current_value += values[j] current_combination.append(j) # 检查当前组合是否满足背包容量限制,并更新最优解 if current_weight <= capacity and current_value > max_value: max_value = current_value best_combination = current_combination return max_value, best_combination ``` 使用示例: ```python weights = [2, 3, 4, 5] values = [3, 4, 5, 6] capacity = 8 max_value, best_combination = brute_force_knapsack(weights, values, capacity) print("Max value:", max_value) print("Best combination:", best_combination) ``` 输出结果: ``` Max value: 9 Best combination: [0, 2] ``` 在上述示例中,我们定义了一个`brute_force_knapsack`函数,它接受物品重量列表`weights`、价值列表`values`和背包容量`capacity`作为输入。函数使用嵌套的循环来枚举所有可能的物品组合,并计算它们的总重量和总价值。然后,它检查当前组合是否满足背包容量限制,并更新最优解。最后,函数返回最大价值和最佳物品组合。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值