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