0-1背包问题【动态规划/跳跃点算法】
动态规划
已知
- n 个物品
- w[i] 为第i个物品的重量
- v[i] 为第i个物品的价值 (i=1,2,3……n)
- W 为背包容量
设定
- j 为背包剩余容量
- m(i,j) 代表背包余量j,从第一个物品到第i个物品的最优价值
- x[i]=0||1
x[i]值为0代表背包不装入第i个物品,值为1代表装入第i个物品
分析
不装入第i个物品时的价值:m(i-1,j)
m(i−1,j-w[i]x[i]) 由于x[i]=0,所以该式化简为:m(i-1,j)
装入第i个物品得到的价值:m(i-1,j-w[i]x[i])+v[i]
步骤
1.w[i] 记录第i个商品的载重量,v[i] 记录第i个商品对应的收益,m[i,j]为从第一个到第i个商品的最优价值
2.如果背包剩余容量j小于当前商品重量w[i],则商品无法放入背包,收益不变, 否则比较装入该商品和不装该商品,哪种情况获得的收益更大,记录最大收益值
3,对每个商品的从背包的1 到 W的承重情况循环步骤2,得到对应的最大收益,
图解过程见下链接:
01背包问题 动态规划
01背包问题
python代码
N = 5 #商品的种类
W = 11 #背包的承重
w = [0,1,2,5,6,7] #商品的承重,不使用 w[0]
v = [0,1,6,18,22,28] # 商品的价值,不使用 v[0]
# 创建一个大小为(W+1)*(N+1)的二维列表,记录统计数据
result = [[0 for col in range(W+1)] for row in range(N+1)]
a= [0 for b in range(W+1)]
print(a)
# 设置初始值为0的X列表,代表背包中为空的状态
#X = [0] * N
# 动态规划算法解决01背包问题
def knapsack():
# 逐个遍历每个商品
for i in range(1, N + 1):
# 求出从 1 到 W 各个承重对应的最大收益
for j in range(1, W + 1):
# 如果背包承重小于商品总重量,则该商品无法放入背包,收益不变
if j < w[i]:
result[i][j] = result[i - 1][j]
# 比较装入该商品和不装该商品,哪种情况获得的收益更大,记录最大收益值
else:
result[i][j] = max(result[i - 1][j], v[i] + result[i - 1][j - w[i]])
# 追溯选中的商品
def selection():
a=W
i=N
while i>0:
if result[i][a]!=result[i-1][a]:
#X[i-1]=1
print(" 第%i个商品,质量-价值 序列为(%d,%d) "%(i,w[i],v[i]))
a=a-w[i]
# else:
# X[i-1]=0
i-=1
knapsack()
print("最大收益为 %d" % (result[N][W]))
print("装入背包的商品为:")
selection()
参考链接:http://c.biancheng.net/algorithm/01-knapsack.html
补充知识点(踩的坑)
创建一个大小为(W+1)*(N+1)的二维列表,记录统计数据
result = [[0 for col in range(W+1)] for row in range(N+1)]
不能写成
result = [[0] * (W+1)] * (N+1)
因为[0] (W+1)是一个一维数组的对象, (N+1)的话只是把对象的引用复制了(N+1)次
但是创建一位数组两种方法都可以
a= [0 for b in range(W+1)]
a=[0] * (W+1)
参考:https://www.cnblogs.com/coderzh/archive/2008/05/18/1201993.html
跳跃点算法
设定
p(i)代表m(i,j)的跳跃点点集
q(i)代表 m(i,j-w[i])+v[i]的跳跃点点集合
1.其中q(i) 可由p(i)的每一个点加上(w[i],v[i])得到。
2. p ( i − 1 )并 上 q ( i − 1 )然后减去受控跳跃点(即必然不合法的点,同 w 下 v 非最大的点和w较大但是v较小,还有)即为 p(i)
即跳跃点图必须是阶梯上升的
分析
设定:
W=8
wi=3,8,4
vi=6,9,5
p[0]={(0,0)}
p[1]={(0,0),(3,6)}
p[2]={ p[1] ∪ ( p[1] ⊕ (w[1],v[1]) )}
={(0,0),(3,6),(8,9)}
同理:
p[3]={(0,0),(3,6)(7,11)}
步骤
1.w[i] 记录第i个商品的载重量,v[i] 记录第i个商品对应的收益,m[i,j]为从第一个到第i个商品的最优价值.p[i]代表m(i,j)的跳跃点点集,q[i]代表 m(i,j-w[i])+v[i]的跳跃点点集合
2.对于不装当前商品的所有装包情况集合p,每种情况加上该商品的承重和价值后,若该情况的所需容量小于背包容量,则并入集合q .否则丢弃,得到装上当前商品的所有情况集合即q[i-1],然后两种情况的集合合并,除去受控点,得到不装下一个商品的所有装包情况集合即p[i]
3.对每个商品种类循环步骤2,最后可得到装入背包的最优值
4.记录好表之后,从终态开始往回倒找解路径即可。
代码
#跳跃点算法
w = [3,5,6,8,9,11] #商品的承重
v = [7,12,18,22,25,27] # 商品的价值
N = len(w) #商品的种类
Capacity = 19 #背包的承重
def merge_points(p,q):
p_len=len(p)
q_len=len(q)
# print(q)
# print(q_len)
i=j=0
merge_points =[]
while i<p_len and j<q_len:
if p[i][0]<q[j][0]:
merge_points.append(p[i])
if p[i][1]>=q[j][1]:
j+=1
i += 1
else:
merge_points.append(q[j])
if p[i][1]<=q[j][1]:
i += 1
j+=1
while i<p_len:
if p[i][0]>merge_points[-1][0] and p[i][1]>merge_points[-1][1]:
merge_points.append(p[i])
i+=1
while j<q_len:
if q[j][0]>merge_points[-1][0] and q[j][1]>merge_points[-1][1]:
merge_points.append(q[j])
j+=1
return merge_points
def knapspack2():
jump_p=[[] for b in range(N+1)]
jump_q=[[] for a in range(N+1)]
jump_p[0].append((0,0))
for i in range(1,N+1):
jump_q[i-1]=[(point[0]+w[i-1],point[1]+v[i-1]) for point in jump_p[i-1] if point[0]+w[i-1]<=Capacity]
jump_p[i]=merge_points(jump_p[i-1],jump_q[i-1])
return jump_p
def selection():
vector =[0 for x in range(N)]
point =result[N][-1]
for i in range(N,0 ,-1):
temp_p=(point[0]-w[i-1],point[1]-v[i-1])
if temp_p in result[i-1]:
print(" 第%d个商品,质量-价值 序列为(%d,%d) "%(i,w[i-1],v[i-1]))
vector[i-1]=1
point =temp_p
result =knapspack2()
print("最大收益为:",result[N][-1][1])
print("装入背包的商品为:")
selection()