0/1背包问题[python]

来源:互联网 发布:淘宝客服外包怎么样 编辑:程序博客网 时间:2024/05/16 01:12
 

问题描述

    假设我们有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



0 0
原创粉丝点击