大家好,欢迎来到IT知识分享网。
背包问题指这样一类问题,题意往往可以抽象成:给定一组物品,每种物品都有自己的重量和价格,在限定的总重量内,我们如何选择,才能使得物品的总价格最高。
就ACM或者其它算法竞赛而言,背包问题可以分为8种类型,其中最基础的是0/1背包问题。作为动态规划的典型问题,其状态转移方程往往需要认真理解并能自行推出。这八种问题分别为:0/1背包问题、完全背包问题、多重背包问题、混合三种背包问题、二维费用背包问题、分组背包问题、有依赖的背包问题、求背包问题的方案总数。
01背包问题
给定\(n\)种物品和一个容量为\(C\)的背包,物品\(i\)的重量是\(w_i\),其价值为\(v_i\)。问:应该如何选择装入背包的物品,使得装入背包中的物品的总价值最大?
使用动态规划,阶段就是“物品的件数”,状态就是“背包剩下的容量”,那么很显然 \(f[ i , v ]\) 就设为从前\(i\)件物品中选择放入容量为\(j\)的背包最大的价值。那么状态转移方程为:
# 动态规划 时间: O(n*C) 空间:O(n*C)
class Solution():
def knapSack(self,w,v,C):
n = len(w)
dp = [[0 for j in range(C+1)] for i in range(n+1)]
for i in range(n+1):
for j in range(C+1):
if i==j==0:
dp[i][j] = 0
elif j==0:
dp[i][0] = 0
elif i==0:
dp[0][j] = -float('inf')
else:
if w[i-1]>j:
dp[i][j] = dp[i-1][j]
else:
dp[i][j] = max(dp[i-1][j],dp[i-1][j-w[i-1]]+v[i-1])
return dp[-1][-1]
v = [8, 10, 6, 3, 7, 2]
w = [4, 6, 2, 2, 5, 1]
C = 12
res = Solution().knapSack(w,v,C)
print('res',res)
实际上,空间可优化到\(O(C)\):
# 动态规划 空间优化 时间: O(n*C) 空间:O(C)
class Solution():
def knapSack(self,w,v,C):
n = len(w)
dp = [0 for i in range(C+1)]
for i in range(1,n+1): # 逆序,避免要使用的子状态收到影响。
for j in reversed(range(w[i-1],C+1)): #注意:当dp[j]依赖于<j的数,需要先更新后面的,即j从大向下更新
dp[j] = max(dp[j],dp[j-w[i-1]]+v[i-1])
return dp[-1]
v = [8, 10, 6, 3, 7, 2]
w = [4, 6, 2, 2, 5, 1]
C = 12
res = Solution().knapSack(w,v,C)
print('res',res)
另外,当\(C\)很大时,也可以按照价值来进行dp,算法复杂度是\(O(n*m)\) .其中\(m\)是背包价值的总和,所以很显然,背包价值范围很小或者\(C\)极大,就用这个方法(另一种情况,当w为小数时,也应该用这种方法)。
#动态规划
class Solution():
def knapSack(self,w,v,C):
sum_v = sum(v)
n = len(w)
dp = [float('inf') for i in range(sum_v+1)]
dp[0] = 0
for i in range(1,n+1):
for j in reversed(range(v[i-1],sum_v+1)): # dp[i] = k,表示的是,已经装了价值为 i 的物品所需要的最小体积k。
dp[j] = min(dp[j],dp[j-v[i-1]]+w[i-1])
for i in reversed(range(sum_v+1)):
if dp[i] <= C:
return i
v = [8, 10, 6, 3, 7, 2]
w = [4, 6, 2, 2, 5, 1]
C = 12
res = Solution().knapSack(w,v,C)
print('res',res)
还有一种DFS+剪枝的方法,适用于物品个数比较少,但是背包容量C和物品价值v都太大了
# DFS+剪枝
class Solution():
def knapSack(self,w,v,C):
n = len(w)
sum_v = v
sum_w = w
for i in range(1,len(v)):
sum_v[i] = sum_v[i-1]+v[i]
sum_w[i] = sum_w[i-1]+w[i]
self.ans = 0
def dfs(a,weight,val):
if(val > self.ans):
self.ans = val
if a==-1:
return
for i in reversed(range(a+1)):
if weight+sum_w[i]<=C and val+sum_v[i]>self.ans: #由于数据是排列好的,从大的数据开始DFS(这一点很重要,是常用的剪枝方法),用辅助数组存放前i项的和,只有weight+sum_w[i]<=C and val+sum_v[i]>self.ans的分支才有可能产生更大的值,这样剪枝来降低时间复杂度。
dfs(i-1,weight+w[i],val+v[i])
dfs(n-1,0,0)
return self.ans
v = [2, 3, 6, 8, 7, 10]
w = [1, 2, 2, 4, 5, 6]
C = 12
res = Solution().knapSack(w,v,C)
print('res',res)
实际使用中,需要根据真实场景选择不同的方法。
完全背包
有\(N\)种物品和一个容量为\(C\)的背包,每种物品都有无限件可用。第\(i\)种物品的费用是\(w[i]\),价值是\(v[i]\)。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
同样的,使用动态规划,可以将二位空间优化到一维空间。
# 动态规划 二维空间-一维空间
class Solution():
def knapSack(self,w,v,C):
n = len(w)
dp = [[0 for j in range(C+1)] for i in range(n+1)]
for i in range(1,n+1):
for j in range(1,C+1):
if w[i-1]>j:
dp[i][j] = dp[i-1][j]
else:
dp[i][j] = max(dp[i-1][j],dp[i][j-w[i-1]]+v[i-1])
return dp[-1][-1]
def knapSack2(self,w,v,C):
n = len(w)
dp = [0 for i in range(C+1)]
for i in range(1,n+1):
for j in range(w[i-1],C+1):
dp[j] = max(dp[j],dp[j-w[i-1]]+v[i-1])
return dp[-1]
w = [2, 3, 7]
v = [1, 1, 1]
C = 11
res = Solution().knapSack(w,v,C)
print('res',res)
还有一种,要求价值总和最大,且总重量恰好等于背包容量C
class Solution():
def knapSack(self,w,v,C):
n = len(w)
dp = [-float('inf') for i in range(C+1)]
dp[0] = 0
for i in range(1,n+1):
for j in range(w[i-1],C+1):
dp[j] = max(dp[j],dp[j-w[i-1]]+v[i-1])
return dp[-1] if dp[-1]!=-float('inf') else -1
w = [5 ,6, 7]
v = [1, 1, 1]
C = 9
res = Solution().knapSack(w,v,C)
print('res',res)
假设价值总和最小,且总重量恰好等于背包容量C:
#类似于 Leetcode322. Coin Change
class Solution():
def knapSack(self,w,v,C):
n = len(w)
dp = [float('inf') for i in range(C+1)]
dp[0] = 0
for i in range(1,n+1):
for j in range(w[i-1],C+1):
if dp[j-w[i-1]]!='float':
dp[j] = min(dp[j],dp[j-w[i-1]]+v[i-1])
return dp[-1] if dp[-1]!=float('inf') else -1
w = [2, 3, 7]
v = [1, 1, 1]
C = 11
res = Solution().knapSack(w,v,C)
print('res',res)
多重背包
多重背包(MultiplePack): 有\(N\)种物品和一个容量为\(C\)的背包。第\(i\)种物品最多有\(n[i]\)件可用,每件费用是\(w[i]\),价值是\(v[i]\)。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
可将多重背包进行二进制分解,然后类似01背包问题求解
class Solution():
def knapSack(self,w,v,n,C):
len_ = len(w)
w_ = []
v_ = []
for i in range(len_):
j = 1
while j<=n[i]:
w_.append(w[i]*j)
v_.append(v[i]*j)
n[i] -= j
j <<= 1
if n[i]>0:
w_.append(w[i]*n[i])
v_.append(v[i]*n[i])
return self.knapSack01(w_,v_,C)
#01背包问题
def knapSack01(self,w,v,C):
n = len(w)
dp = [0 for i in range(C+1)]
dp[0] = 0
for i in range(1,n+1):
for j in reversed(range(w[i-1],C+1)):
dp[j] = max(dp[j],dp[j-w[i-1]]+v[i-1])
return dp[-1]
n = [1, 1, 1, 1, 1, 8]
v = [8, 10, 6, 3, 7, 2]
w = [4, 6, 2, 2, 5, 1]
C = 12
res = Solution().knapSack(w,v,n,C)
print('res',res)
混合三种背包问题
有的物品只可以取一次(01 背包),有的物品可以取无限次(完全背包),有的物品可以取的次数有一个上限(多重背包)。
可以转化为01背包和完全背包的混合问题。
class Solution():
def knapSack(self,w,v,n,C):
len_ = len(w)
w_ = []
v_ = []
n_ = []
for i in range(len_):
if n[i] == -1:
w_.append(w[i])
v_.append(v[i])
n_.append(-1)
else:
j = 1
while j<=n[i]:
w_.append(w[i]*j)
v_.append(v[i]*j)
n_.append(1)
n[i] -= j
j <<= 1
if n[i]>0:
w_.append(w[i]*n[i])
v_.append(v[i]*n[i])
n_.append(1)
print((w_,v_,n_,C))
return self.knapSack01(w_,v_,n_,C)
#01背包和完全背包的混合问题
def knapSack01(self,w,v,n,C):
dp = [0 for i in range(C+1)]
dp[0] = 0
for i in range(1,len(w)+1):
if n[i-1] == 1:
for j in reversed(range(w[i-1],C+1)):
dp[j] = max(dp[j],dp[j-w[i-1]]+v[i-1])
else:
for j in range(w[i-1],C+1):
dp[j] = max(dp[j],dp[j-w[i-1]]+v[i-1])
return dp[-1]
n = [1, 1, 1, 1, 6, -1]
v = [8, 10, 6, 3, 7, 2]
w = [4, 6, 2, 2, 5, 1]
C = 12
res = Solution().knapSack(w,v,n,C)
print('res',res)
二维费用的背包问题
二维费用的背包问题是指:对于每件物品,具有两种不同的费用;选择这件物品必须同时付出这两种代价;对于每种代价都有一个可付出的最大值(背包容量)。 问怎样选择物品可以得到最大的价值。设这两种代价分别为代价1和代价2,第i件物品所需的两种代价分别为\(a[i]\)和\(b[i]\)。两种代价可付出的最大值 (两种背包容量)分别为\(V\)和\(U\)。物品的价值为\(w[i]\)。
最常见的是对物品个数的限制。
class Solution():
def knapSack(self,w1,w2,v,g,C1,C2):
n = len(w1)
dp = [[0 for k in range(C2+1)]for j in range(C1+1)]
for i in range(1,n+1): # 逆序,避免要使用的子状态收到影响。
for j in reversed(range(w1[i-1],C1+1)): #注意:当dp[j]依赖于<j的数,需要先更新后面的,即j从大向下更新
for k in reversed(range(w2[i-1],C2+1)):
dp[j][k] = max(dp[j][k],dp[j-w1[i-1]][k-w2[i-1]]+v[i-1])
# print('dp',dp)
return dp[-1][-1]
v = [8, 10, 6, 3, 7, 2]
w1 = [4, 6, 2, 2, 5, 1]
w2 = [2, 4, 3, 1, 2, 2]
C1 = 12
C2 = 6
res = Solution().knapSack(w1,w2,v,g,C1,C2)
print('res',res)
分组的背包问题
有\(N\)件物品和一个容量为\(V\)的背包。第\(i\)件物品的费用是\(c[i]\),价值是\(w[i]\)。这些物品被划分为若干组,每组中的物品互相冲突,最多选一件。 求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
# 动态规划 时间: O(n*C) 空间:O(n*C)
class Solution():
def knapSack(self,w,v,g,C):
w_ = {}
v_ = {}
n = len(w)
groups_num = 0 #分租数目
for i in range(n):
if g[i] not in w_:
w_[g[i]] = []
v_[g[i]] = []
groups_num += 1
w_[g[i]].append(w[i])
v_[g[i]].append(v[i])
dp = [0 for i in range(C+1)]
for i in range(groups_num):
for j in reversed(range(C+1)):
for p in range(len(w_[i])):
if (j >= w_[i][p]):
dp[j] = max(dp[j], dp[j - w_[i][p]] + v_[i][p])
return dp[-1]
v = [8, 10, 6, 3, 7, 2]
w = [4, 6, 2, 2, 5, 1]
g = [0, 0, 1, 1, 2, 2] #组数 组号需要从0开始编号
C = 12
res = Solution().knapSack(w,v,g,C)
print('res',res)
有依赖的背包问题
这种背包问题的物品间存在某种“依赖”的关系。也就是说,物品\(i\)依赖于物品\(j\),表示若选物品\(i\),则必须选物品\(j\)。为了简化起见,我们先设没有某个物品既依赖于别的物品,又被别的物品所依赖;另外,没有某件物品同时依赖多件物品。
泛化物品
在背包容量为\(V\)的背包问题中,泛化物品是一个定义域为0… V中的整数的函数h,当分配给它的费用为\(v\)时,能得到的价值就是\(h(v)\)。
参考:
01背包总结
常见的动态规划问题分析与求解
背包问题九讲
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://yundeesoft.com/30956.html