0-1背包问题python版

0-1背包问题描述

假设有n件物品,编号为1, 2...n。编号为i的物品价值为vi,它的重量为wi。简化问题,都是整数值。有一个背包,它能够承载的重量是W。我们希望往包里装这些物品,使得包里装的物品价值最大化,那么该如何来选择装的东西呢? 假定选取的物品每个都是独立的,不能选取部分。也就是要么选取,要么不能选取,不能只选取一个物品的一部分。

初步分析

对于这n个物品,每个物品可能会选,也可能不选,那么总共就可能有2^n种组合选择方式。整体的时间复杂度就达到指数级别的,不可行。 换一种思路。优先挑选那些单位价格最高的是否可行呢?比如有3种物品,他们的重量和价格分别是10, 20, 30 kg和60, 100, 120。背包可装最大重量为50kg 那么按照单位价格来算的话,我们最先应该挑选的是价格为60的元素,选择它之后,背包还剩下50 - 10 = 40kg。再继续前应该挑选价格为100的元素,这样背包里的总价值为60 + 100 = 160。所占用的重量为30, 剩下20kg。因为后面需要挑选的物品为30kg已经超出背包的容量了。我们按照这种思路能选择到的最多就是前面两个物品。 可是由于有一个背包重量的限制,这里只用了30kg,还有剩下20kg浪费了。选20+30的价值为220,选10+30的价值为180所以不行

动态规划

需要选择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个部分,我们可以得到一个如下的递推公式: 输入图片说明

式(1)表示,把前面i物体装入载重量为0的背包,或者把0个物体装入载重量为j的背包,得到的价值都为0。(2)式表明,如果第i个物体的重量大于背包的载重量,则装入前面i个物体得到的最大价值,与装入前面i – 1个物体得到的最大价值一样(第i个物体没有装入背包)。式(3)表明,当第i个物体的重量小于背包的载重量时,如果把第i个物体装入载重量为j的背包后总价值上升,那么就装入,否则不装入。
有了这个关系,后面的函数结果其实是依赖于前面的结果的。我们只要按照前面求出来最基础的最优条件,然后往后面一步步递推,就可以找到结果了。

我们再来考虑一下具体实现的细节。这一组物品分别有价值和重量,我们可以定义两个数组int[] v, int[] w。v[i]表示第i个物品的价值,w[i]表示第i个物品的重量。为了表示c[i, w],我们可以使用一个int[i][w]的矩阵。其中i的最大值为物品的数量,而w表示最大的重量限制。按照前面的递推关系,c[i][0]和c[0][w]都是0。而我们所要求的最终结果是c[n][w]。所以我们实际中创建的矩阵是(n + 1) x (w + 1)的规格。下面是该过程的一个代码参考实现: python代码1 收藏代码

def bag(n, c, w, v):
    res = [[-1 for j in range(c + 1)] for i in range(n + 1)]
    for j in range(c + 1):
        res[0][j] = 0
    for i in range(1, n + 1):
        for j in range(1, c + 1):
            res[i][j] = res[i - 1][j]
            if j >= w[i - 1] and res[i][j] < res[i - 1][j - w[i - 1]] + v[i - 1]:
                res[i][j] = res[i - 1][j - w[i - 1]] + v[i - 1]
    return res


def show(n, c, w, res):
    print('最大价值为:', res[n][c])
    x = [False for i in range(n)]
    j = c
    for i in range(1, n + 1):
        if res[i][j] > res[i - 1][j]:
            x[i - 1] = True
            j -= w[i - 1]
    print('选择的物品为:')
    for i in range(n):
        if x[i]:
            print('第', i, '个,', end='')
    print('')


if __name__ == '__main__':
    n = 5
    c = 10
    w = [2, 2, 6, 5, 4]
    v = [6, 3, 5, 4, 6]
    res = bag(n, c, w, v)
    show(n, c, w, res)

python代码1

# 背包的载重量
m = 33

dp = [[-1 for j in range(m + 1)] for i in range(n)]

for i in range(n):
    dp[i][0] = 0

for j in range(m + 1):
    if w[0] <= j:
        dp[0][j] = p[0]
    else:
        dp[0][j] = 0


def dp_fun(i, j):
    if dp[i][j] != -1:
        return dp[i][j]
    if j >= w[i]:
        dp[i][j] = max(dp_fun(i - 1, j), dp_fun(i - 1, j - w[i]) + p[i])
    else:
        dp[i][j] = dp_fun(i - 1, j)
    return dp[i][j]


print('最大值为:' + str(dp_fun(n - 1, m)))

python代码2

# coding:utf-8
def bag(n, m, w, v):
    res = [[0 for j in range(m + 1)] for i in range(n + 1)]  # n+1 行,m+1列 值为0的矩阵
    for i in range(1, n + 1):
        for j in range(1, m + 1):
            res[i][j] = res[i - 1][j]  # 0->res[0][1]->res[1][1]
            if j >= w[i - 1] and res[i][j] < res[i - 1][j - w[i - 1]] + v[i - 1]:
# 如果res[i-1][j]小于res[i-1][j-w[i-1]]+v[i-1],那么res[i][j]就等于res[i-1][j],否则就等于res[i-1][j-w[i-1]]+v[i-1]
                res[i][j] = res[i - 1][j - w[i - 1]] + v[i - 1]
    return res


def show(n, m, w, res):
    print(u"最大值为%d" % res[n][m])
    x = [False for i in range(n)]
    j = m
    for i in range(n, 0, -1):
        if res[i][j] != res[i - 1][j]:
            x[i - 1] = True
            j -= w[i - 1]
    print(u"选择的物品为")
    for i in range(n):
        if x[i]:
            print(u"第%d个" % (i + 1))


if __name__ == "__main__":
    # n种物品,承重量为m,w物品的重量,v 物品的价值
    n = 4
    m = 5
    w = [2, 1, 3, 2]
    v = [12, 10, 20, 15]
    res = bag(n, m, w, v)
    print(res)
    show(n, m, w, res)

python代码3

import numpy as np
# def solve(vlist,wlist,totalWeight,totalLength):
#     resArr = np.zeros((totalLength+1,totalWeight+1),dtype=np.int32)
#     for i in range(1,totalLength+1):
#         for j in range(1,totalWeight+1):
#             if wlist[i] <= j:
#                 resArr[i,j] = max(resArr[i-1,j-wlist[i]]+vlist[i],resArr[i-1,j])
#             else:
#                 resArr[i,j] = resArr[i-1,j]
#     return resArr[-1,-1]
#
# if __name__ == '__main__':
#     v = [0,60,100,120]
#     w = [0,10,20,30]
#     weight = 50
#     n = 3
#     result = solve(v,w,weight,n)
#     print(result)

def solve2(vlist,wlist,totalWeight,totalLength):
    resArr = np.zeros((totalWeight)+1,dtype=np.int32)
    for i in range(1,totalLength+1):
        for j in range(totalWeight,0,-1):
            if wlist[i] <= j:
                resArr[j] = max(resArr[j],resArr[j-wlist[i]]+vlist[i])
    return resArr[-1]

if __name__ == '__main__':
    v = [0,60,100,120]
    w = [0,10,20,30]
    weight = 50
    n = 3
    result = solve2(v,w,weight,n)
    print(result)


至此,我们对于这种问题的解决方法已经分析出来了。它的总体时间复杂度为O(nw) ,其中w是设定的一个重量范围,因此也可以说它的时间复杂度为O(n)。

猜你喜欢

转载自my.oschina.net/u/3726752/blog/1786295