题目:
背包容量为12,有物品1,2可供选择
物品 | 占用空间 | 价值 | 数量 |
---|---|---|---|
物品1 | 2 | 3 | 5 |
物品2 | 3 | 4 | 2 |
求在背包中装入价值最大的解决方案。
2019/2/27
补充:背包问题分为9类,上面的问题严格来说属于完全背包问题,有特殊解法,参考https://www.cnblogs.com/jbelial/articles/2116074.html。
使用完全背包问题的解法后面附上。
思路:
- 5个物品1可等价为5个物品,2个物品2可以等价为2个物品,这样一共有7个物品,转换为7个物品的0-1背包问题。
-
对于第i个物品,背包问题的基本递推公式如下:
- Wi>W,装不下了,则M(i, W)=M(i-1, W);
- Wi<=W,那么选择装或者不装,则M(i, W)=max{M(i-1, W) , M(i-1, W-Wi)+Vi}。
- 装第i个物品的价值由第i-1个物品有关,因此可以想到递归;同时,也可以选择将前i-1个物品的状态保存,使用动态规划。
结果:
max_val:17
num1:3, num2:2
由于本题目只有两类物品,枚举当然也是合适的,但是如果物品种类数t>=3,那么时间复杂度肯定是无法接受的。
枚举def func1(x1,x2,space):
max_val,num1,num2=0,[],[]
for i in range(x1[2]+1):
for j in range(x2[2]+1):
if i*x1[0]+j*x2[0]<=space:
if i*x1[1]+j*x2[1]>max_val:
num1,num2=[i],[j]
max_val=i*x1[1]+j*x2[1]
elif i*x1[1]+j*x2[1]==max_val:
num1.append(i)
num2.append(j)
max_val=i*x1[1]+j*x2[1]
print('max_val:%d'%max_val)
for i in range(len(num1)):
print('num1:%d, num2:%d'%(num1[i],num2[i]))
x1=[2,3,5]
x2=[3,4,2]
space=12
func1(x1,x2,space)
注:物品种类数为n,数量分别为t1,t2,…,tn,使用枚举需要的时间复杂度O (∏i=1nti\prod_{i=1}^{n} t_i∏i=1nti),空间复杂度O (1).
递归:按照树深度搜索从上到下遍历
由于题目的物品数量比较多,不便画图,这里采取一个物品数量更少的01背包问题来解释思路。
背包容量为5,有物品1,2,3,4可供选择
物品 | 占用空间 | 价值 | 数量 |
---|---|---|---|
物品1 | 2 | 3 | 1 |
物品2 | 1 | 2 | 1 |
物品3 | 3 | 4 | 1 |
物品4 | 2 | 2 | 1 |
用w(w,v)状态变量表示选择第i个物品时的剩余空间和当前总价值。如下图,选择左子树表示选择放入当前物品,选择右子树表示不放入当前物品。
由图可以看出,选择物品1,2,4或者物品1,3可同时达到最大价值7;因此要求最大价值,实际上就是返回叶子节点中的最大值以及对应的搜索路径。
def func2(info, w):
max_val,state=bag_01(info,w)
num=set()
for clue in state:
count_a,count_b=0,0
for i in range(len(clue)):
if clue[i]>0:
if i<5:
count_a+=1
else:
count_b=count_b+1
num.add((count_a,count_b))
print('max_val:%d'%max_val)
for elem in num:
print('num1:%d, num2:%d'%(elem[0],elem[1]))
def bag_01(info,w):
if info==[] or w[0]<info[0][0]:
return w[1],[[]]
x0,state0=bag_01(info[1:],w)
x1,state1=bag_01(info[1:],[w[0]-info[0][0],w[1]+info[0][1]])
if x0>x1:
for clue in state0:
clue.insert(0,0)
return x0,state0
elif x0<x1:
for clue in state1:
clue.insert(0,1)
return x1,state1
else:
for clue in state0:
clue.insert(0,0)
for clue in state1:
clue.insert(0,1)
state0.extend(state1)
return x0, state0
info=[[2,3] for i in range(5)]+[[3,4] for i in range(2)]
w=[12,0]
func2(info, w)
注:此方法中func中的两层遍历是为了将每个“单独的物品”还原到原来的两类物品。此方法时间复杂度 O (log(∑i=1nti\sum_{i=1}^{n} t_i∑i=1nti)),空间复杂度O (log2log^2log2(∑i=1nti\sum_{i=1}^{n} t_i∑i=1nti)).
动态规划:
动态规划求解背包问题变成了一个填表问题,仍然以递归算法中的背包问题为例:
给与空间 | 0 | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|---|
物品0 | 0 | 0 | 0 | 0 | 0 | 0 |
物品1 | 0 | 0 | 3 | 3 | 3 | 3 |
物品2 | 0 | 2 | 3 | 5 | 5 | 5 |
物品3 | 0 | 2 | 3 | 5 | 6 | 7 |
物品4 | 0 | 2 | 3 | 5 | 6 | 7 |
其中物品0和给与空间0只是为了递推的时候更方便,简化边界条件判断。
下面对表格的填写进行分析
- 表格的每一行表示分别给与第i个物品0,1,2,3,4,5的空间,选择放入第i个物品与否,达到的最大价值W(i,j);
- 第i个物品在空间给与空间为j时,如果w(i)>j,则W(i,j)=W(i-1,j);否则,W(i,j)=max{ v(i)+W(i-1, j-w(i)), W(i-1,j) };
- 例如:对于物品1,在给与空间为0,1时,放不下,则最大价值为W(i-1,j)=0,在给与空间为2时,W(1,2)=max{ v(1)+W(0,0), W(0,2)}=max{3,0}=3,给与空间为3,4,5时W=3;对于物品2,给与空间为0,1,2,3,4,5时对应的最大价值分别为0,2,3,5,5,5。
于是表格填写完也就找到了最大价值
但是如何找到具体怎么选择物品呢?
表格中存在答案。
表格是很有规律的,
- 最大价值必定在表格最右下角;
- 对于每个物品选择与否,取决于对应的状态变量w(w,v)是否存在。
分析:用(x,y)i表示对于物品i的(给与空间,最大价值),对于物品4,(5,7)4,如果选择物品4,那么对于物品3,(3,5)3,如果不选择物品4,则对于物品3,(5,7)3。可以发现在物品3中,第3列最大价值为5,第5列最大价值为7,说明这两种都是合理的。于是从表的最后一行逐层往上反推便可以得到分配方案,递归便可以解决这个问题。
def func3(info,space):
info=[[0,0]]+info
w=[[0 for i in range(space+1)] for j in range(len(info))]
find_maxval(w, info)
max_val=w[-1][-1]
state=find_state(w, info, len(w)-1, len(w[0])-1)
num=set()
for clue in state:
count_a,count_b=0,0
for i in range(len(clue)):
if clue[i]>0:
if i<5:
count_a+=1
else:
count_b=count_b+1
num.add((count_a,count_b))
print('max_val:%d'%max_val)
for elem in num:
print('num1:%d, num2:%d'%(elem[0],elem[1]))
def find_maxval(w, info):
for i in range(1,len(w)):
for j in range(1,len(w[0])):
if info[i][0]>j:
w[i][j]=w[i-1][j]
else:
w[i][j]=max(w[i-1][j], w[i-1][j-info[i][0]]+info[i][1])
def find_state(w,info,row,col):
if row==0:
return [[]]
if w[row-1][col]>w[row-1][col-info[row][0]]+info[row][1] or \
info[row][0]>col:
clue=find_state(w,info,row-1,col)
for elem in clue:
elem.append(0)
return clue
elif w[row-1][col]<w[row-1][col-info[row][0]]+info[row][1]:
clue=find_state(w,info,row-1,col-info[row][0])
for elem in clue:
elem.append(1)
return clue
else:
clue0=find_state(w,info,row-1,col)
clue1=find_state(w,info,row-1,col-info[row][0])
for elem in clue0:
elem.append(0)
for elem in clue1:
elem.append(1)
return clue0+clue1
info=[[2,3] for i in range(5)]+[[3,4] for i in range(2)]
space=12
func3(info,space)