01背包问题
01背包问题相信大家都很熟悉,问题描述如下:
有一个背包,它的容量为C,现在有 n n n种不同的物品,编号为 0... n − 1 0...n-1 0...n−1,其中每一件物品的重量为 w ( i ) w(i) w(i),价值为 v ( i ) v(i) v(i),问可以向这个背包中放那些物品,使得在不超过背包容量的基础上,物品的总价值最大。
我们假设求解问题状态为:
F
(
n
,
c
)
F(n,c)
F(n,c)考虑将
n
n
n个物品放进容量为
C
C
C的背包,使得价值最大。
问题状态转移为一下两种情况:
- F ( i , c ) = F ( i − 1 , c ) F(i,c)=F(i-1,c) F(i,c)=F(i−1,c)
-
F
(
i
,
c
)
=
v
(
i
)
+
F
(
i
−
1
,
c
−
w
(
i
)
)
F(i,c)=v(i)+F(i-1,c-w(i))
F(i,c)=v(i)+F(i−1,c−w(i))
情况1中,我们假定当前物品i直接丢弃不放进背包
情况2中,我们假定当前物品i直接放进背包
在求解 F ( i , c ) F(i,c) F(i,c) 的过程中只存在这两种情况,下面我们先使用递归自顶向下求解:
class Solution:
def knapsack01(self, weight, value, c):
return self.getMax(weight, value, c, len(weight) - 1)
def getMax(self, w, v, c, index):
if index < 0 or c <= 0:
return 0
# 第一种情况,不考虑当前index指向的物品,直接看index+1物品的价值和重量
res = self.getMax(w, v, c, index - 1)
# 第二种情况,直接将当前index指向物品放入背包
if c >= w[index]:
res = max([res, v[index] + self.getMax(w, v, c - w[index], index - 1)])
return res
在递归过程中,我们发现存在很多重复计算的子问题,而这些子问题是存在于物品价值v和背包容量之间相互对应的。下面我们使用基于记忆搜索的方法,记录子问题的求解记录。
class Solution:
def knapsack01_memo(self, weight, value, c):
self.memo = [[-1 for _ in range(c + 1)] for _ in range(len(weight))]
return self.getMax_memo(weight, value, c, len(weight) - 1)
def getMax_memo(self, w, v, c, index):
if index < 0 or c <= 0:
return 0
if self.memo[index][c] is not -1:
return self.memo[index][c]
# 第一种情况,不考虑当前index指向的物品,直接看index+1物品的价值和重量
res = self.getMax_memo(w, v, c, index - 1)
# 第二种情况,直接将当前index指向物品放入背包
if c >= w[index]:
res = max([res, v[index] + self.getMax_memo(w, v, c - w[index], index - 1)])
self.memo[index][c] = res
return res
通过进一步分析,我们使用dp自顶向上的方法来解决该问题,初始化二维数组
m
e
m
o
memo
memo大小为
w
.
s
i
z
e
w.size
w.size行,
c
c
c列,
m
e
m
o
memo
memo存放过程如下:
将
m
e
m
o
memo
memo列值代表当前背包容量
c
(
j
)
c(j)
c(j),行代表待存放物品个数
w
(
i
)
w(i)
w(i),当i=0时,
m
e
m
o
[
i
]
[
j
]
=
c
(
j
)
>
=
w
(
0
)
?
v
[
i
]
:
0
memo[i][j]=c(j)>=w(0) ? v[i]:0
memo[i][j]=c(j)>=w(0)?v[i]:0 ,即判断当前背包容量是否可以存下
w
(
i
)
w(i)
w(i)。接着
i
>
=
1
i>=1
i>=1时,我们取当前
i
i
i至少可以取到的值以及
v
[
t
i
m
e
]
+
m
e
m
o
[
t
i
m
e
−
1
]
[
c
−
w
[
t
i
m
e
]
]
v[time] + memo[time - 1][c - w[time]]
v[time]+memo[time−1][c−w[time]]中的最大值。
class Solution:
# dp求解背包问题
def knapsack01_dp(self, w, v, c):
memo = [[-1 for _ in range(c + 1)] for _ in range(len(v))]
for i in range(c + 1):
memo[0][i] = v[0] if i >= w[0] else 0
for time in range(1, len(v)):
for c in range(c + 1):
# 情况一 直接计算当前容量至少存放的最大价值
memo[time][c] = memo[time - 1][c]
if c >= w[time]:
memo[time][c] = max([memo[time][c], v[time] + memo[time - 1][c - w[time]]])
return memo[-1][-1]
在上述过程中,我们发现 m e m o memo memo遍历时,每次只使用了最后两行数组,所以我们在空间上对其进行优化,只创建一个两行的数组,当循环次数等于偶数时更新第2行,次数为奇数时更新第一行。
class Solution:
def knapsack01_ADM(self, w, v, c):
memo = [[0 for _ in range(c + 1)] for _ in range(2)]
for i in range(c + 1):
memo[0][i] = v[0] if i >= w[0] else 0
for time in range(1, len(w)):
for cap in range(c + 1):
i, j = (1, 0) if time & 1 else (0, 1)
memo[j][cap] = memo[i][cap]
if cap >= w[time]:
memo[j][cap] = max([memo[j][cap], v[time] + memo[i][c - w[time]]])
return memo[-1][-1]