0-1背包问题的所有变形(python描述)

首先需要讲明白的是,虽然递归进行了一些重复性的计算,但是在某些情况下,其时间复杂度可能会比非递归的算法要低的多。这一点需要注意!

1.背包问题

题目:背包容量为10,有以下4中物品,体积分别为:2,3,4,7,它们所对应的价值为1,4,7,9.问:怎么拿才能拿的价值尽可能大?

递归写法:

#w列表为物品对应的体积,相应的v对应的是物品的价值
w = [2,3,4,7]
v = [1,4,7,9]
#物品编号数以及背包的容量
pro_num = len(w)-1
weight = 10
#递归算法
def most_value(total_num,pro_num):
	#表示没有物品可以拿或者背包容量小于2,也就是没法拿东西,所以拿的价值为0
	#在这里需要注意的是这个2对应的是上面列表中体积最小的值,因为递归的时候需要有一个出口,而出口必须要是最小的
    if pro_num < 0 or total_num < 2:
        return 0
    #表示只剩下一个物品可以拿而且你的背包容量比这个大的时候,就只能拿这个东西了,所以得到的价值为相对应的值
    if pro_num == 0 and total_num >= 2:
        return v[0]
    #表示物品有很多,从编号往后的开始往前面拿,但是我编号最后的我拿不了,所以,直接剔除掉编号最后的物品
    if pro_num > 0 and total_num < w[pro_num]:
        return most_value(total_num,pro_num-1)
    #其他的情况就是老夫拿的了最后一个,我可以选择拿,也可以选择不拿,两种情况
    else:
        return max(most_value(total_num,pro_num-1),most_value(total_num-w[pro_num],pro_num-1)+v[pro_num])
print(most_value(weight,pro_num))

非递归写法
思想:类似于一种画表的方法,有小到大。比如当只存在物品1的时候,容量从0一直到10,所装的最大价值均存在一个 pre_result[]列表之中,然后当存在两个物品的时候,就分两种情况,一种是不拿,就直接等于它表格的上一个内容,如果拿的话,就等于拿的物品的值加上和上一行表格中对应的新的容量的值之和。

w = [0,2,3,4,7]
v = [0,1,4,7,9]
total_weight = 10
total_num = len(w)
#预列表存储前一行的值
pre_result = []
for i in range(total_weight+1):
    pre_result.append(0)
x = [[0] for i in range(total_num)]
#result[ ]该列表存储当前值
result = [0]*len(pre_result)
x[0][0:] = result[0:]
#一下两个循环表示对表格进行填写,上面的循环是物品依次增加,下面的是背包容量依次增加。
for i in range(1,total_num,1):
    for j in range(total_weight+1):
        if j < w[i]:
            result[j] = pre_result[j]
        else:
            result[j] = max(pre_result[j],pre_result[j-w[i]]+v[i])
    x[i][0:] = result[0:]
    pre_result[0:] = result[0:]
    #输出列表的最后一行,最后一个值也就是所能拿的最大值
    print(result)

##找出背包的编号。思想:与表格的上一行的同等情况相比,如果大于上面的,则表明该行对应的物品一定拿了,否则没拿。这种情况需要用x列表对整个二维数组进行存储,然后从后开始往前遍历。
for i in range(total_num-1,0,-1):
    if x[i][total_weight]>x[i-1][total_weight]:
        print('the bag of ',+i)
        total_weight -= w[i]

运行结果图为:
在这里插入图片描述

2.完全背包问题(物品无限)

当物品无限的时候,只需要在物品有限的基础之上进行修改即可。
递归算法

w = [2,3,4,7]
v = [1,4,7,9]
pro_num = 3
#首先写递归
def most_value(total_num,pro_num):
    if pro_num < 0 or total_num < 2:
        return 0
    #只有刚好等于2的时候,才会拿最小的,如果大于2可能会对该物品进行多次拿取
    elif pro_num == 0 and total_num == 2:
        return v[0]
    #拿不了仍然还是不拿
    elif pro_num > 0 and total_num < w[pro_num]:
        return most_value(total_num,pro_num-1)
    #重点在这:与上面的相比,此时存在3种情况,不拿该项;拿了还想拿该项;拿了不想拿该项。返回其最大值
    else:
        return max(most_value(total_num,pro_num-1),most_value(total_num-w[pro_num],pro_num-1)+v[pro_num],most_value(total_num-w[pro_num],pro_num)+v[pro_num])
print(most_value(10,3))

运行结果是:15
非递归算法:
将上面的物品数据修改一下,便于显示出与不能重复的背包问题的区别。

w = [0,3,3,4,7]
v = [0,2,4,7,9]
total_weight = 10
total_num = len(w)
pre_result = []
for i in range(total_weight+1):
    pre_result.append(0)
x = [[0] for i in range(total_num)]

result = [0]*len(pre_result)
x[0][0:] = result[0:]
for i in range(1,total_num,1):
    for j in range(total_weight+1):
        if j < w[i]:
            result[j] = pre_result[j]
       #与不可重复的背包问题相比,也就是修改了这一步。在寻找最大值的时候,应该加上拿了但是还想拿的那一步,也就是不仅仅和pre_result比较,应增加一个result的,表示拿了还可能会继续拿。
        else:
            result[j] = max(pre_result[j],result[j-w[i]]+v[i],pre_result[j-w[i]]+v[i])
    x[i][0:] = result[0:]
    pre_result[0:] = result[0:]
    print(result)

同样的,在输出编号的时候,与表格下一行与上一行进行比较的时候,如果相同,则本行对应的物品没有取;如果不同,则还应该在本行的减去该物品重量之后,再次进行比较,,因为可能还会拿第二次。

k = total_weight
i = total_num-1
while i > 0:
    while k > 0:
        if x[i][k] > x[i-1][k]:
            print('the bag of ', +i)
            k -= w[i]
        else:
            break
    i -= 1

运行结果图:
在这里插入图片描述

3.多重背包问题(物品限量,非一个,非无限)

由于非递归的算法想的脑壳疼,所以仅写出递归的算法,能力有限,敬请谅解。
题目:有编号分别为a,b,c的三件物品,它们的重量分别是1,2,2,它们的价值分别是6,10,20,他们的数目分别是10,5,2,现在给你个承重为 8 的背包,如何让背包里装入的物品具有最大的价值总和?
画出表格:
在这里插入图片描述
递归算法:

w = [1,2,2]
v = [6,10,20]
#num数组是对应物品的个数
num = [10,5,2]
pro_num = 2
total_num = 8
def most_value(total_num,pro_num,num):
    if pro_num < 0 or total_num < 1:
        return 0
    #和之前的相比,这个地方多了一个,没有物品的情况
    elif pro_num == 0 and total_num == 1 and num[pro_num] > 0:
        return v[0]
    elif pro_num > 0 and (total_num < w[pro_num] or num[pro_num] < 1):
        return most_value(total_num,pro_num-1,num)
   #当选取物品的时候,所对应的num数组的个数要减一
    else:
        num[pro_num] -= 1
        return max(most_value(total_num,pro_num-1,num),most_value(total_num-w[pro_num],pro_num-1,num)+v[pro_num],most_value(total_num-w[pro_num],pro_num,num)+v[pro_num])
print(most_value(total_num,pro_num,num))

运行结果:64

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值