一、问题描述
有N件物品和一个容量为V的背包。第i件物品的体积是c[i],价值是w[i]。求解将哪些物品装入背包可使价值总和最大。
二、解题思路
0/1背包问题是典型的动态规划问题,即每种物品只有两种状态放(1) 或 不放(0) 。对于这个问题如果是从前向后来考虑,其实还是有一定难度,所以我们其实可以从后向前来进行考虑,通过维护一个二维数组来记录每种情况。
对于最后一个物品,如果它的重量是大于背包容量时,那肯定是放不下的,但是当其重量小于背包容量时就存在了可能放入的可能性,因此我们对其进行记录,递归式如下。
然后从后向前对每个物品都进行判断,如果当前物品的重量大于背包容量那么就不容考虑它了,那么到当前物品的最大值仍等于到上一个物品的最大值,但如果物品的重量小于背包的容量时,就存在了物品可能被放入的可能性,就需要再分两种情况来进行讨论,即放入和不放入。
如果不放入,那么背包剩余容量和当前最大价值不改变,仍然等于到上一个物品为止的最大值,如果放入的话,那么到当前物品为止的最大值应该等于到上一个物品为止的最大值加上当前物品的价值,然后背包的剩余容量应减去当前物品的重量,递归式如下。
这道题的思路是比较清晰的,代码实现我这里有两种,只是写法不同但是思路大致是相同的,解法一是从后到前,解法二是从前到后,而且相对来说第二种解法更简洁清晰一些。
三、代码描述
// 解法一:
#include <iostream>
// 查找结果
void traceBack(vector<vector<int>> recordArray, vector<int> weight, int capacity, int quantity){
// 最后的结果数组
vector<int> result = vector<int>(quantity);
// 遍历记录数组来获取结果
// 初始情况时背包的容量就为背包的原始容量
for(int i = 0; i < quantity-1; ++i){
// 如果到当前物品为止的最大值等于到下一个物品为止的最大值
// 那么说明背包中没有放入当前物品 所以直接赋值为 0
if(recordArray[i][capacity] == recordArray[i+1][capacity]){
result[i] = 0;
// 反之如果不相等则说明放入了当前物品
// 此时背包的容量应减去当前物品的重量
}else{
result[i] = 1;
capacity -= weight[i];
}
}
// 因为最后一个物品没有下一个物品
// 我们就只需判断最后一个元素是否为零
// 即可知道最后一个物品是否被放入背包
result[quantity-1] = recordArray[quantity-1][capacity] ? 1 : 0;
for(int res: result)
cout << res << " ";
}
// weight 为每个物品对应的重量
// value 为每个物品对应的价值
// capacity 为背包的容量
// quantity 为物品的个数
void knapsack(vector<int> weight, vector<int> value, int capacity, int quantity){
// 创建记录数组
// 其中行数为物品的个数 下标从 0 到 quantity-1
// 列数为背包的容量加一 下标从 0 到 capacity (这个主要是考虑背包容量的范围是 0~capacity)
// 因此对应的 recordArray[i][j] 的含义为 当第i个物品时背包容量为j时的最大价值
// 所以 recordArray[0][capacity] 保存的就是最终背包的最大值
vector<vector<int>> recordArray = vector<vector<int>>(quantity, vector<int>(capacity+1));
// 首先对最后一个物品进行单独处理
// 选取容量分解点为最后一个物品的重量的背包容量的最小值
int maxCapacity = min(weight[quantity-1], capacity);
// 当当前判断的为最后一个物品时 i = quantity-1
// 分为两种情况 当 j < weight[quantity-1] 时最后一个物品无法被放下 所以价值都为零
for(int j = 0; j < maxCapacity; ++j)
recordArray[quantity-1][j] = 0;
// 当 j >= weight[quantity-1] 时 那么从最后一个物品的重量开始往后的容量都可以放入最后一个物品
// 因此我们可以直接将最后一个物品重量之后的背包容量(j >= weight[quantity-1])都赋值为最后一个物品的价值
for(int j = weight[quantity-1]; j <= capacity; ++j)
recordArray[quantity-1][j] = value[quantity-1];
// 计算除最后一个物品和第一个物品以外的其它物品
// 这里需要注意的是 我们是从后向前来进行计算的
// recordArray 也就从结尾处到当前位置的最大值积累
for(int i = quantity-2; i > 0; --i){
// 跟上面一样求取分界点 并分两种情况讨论
int maxCapacity = min(weight[i], capacity);
// 当当前 j < 当前物品的重量时 那么肯定就是当前情况下背包中已经无法放下此物品
// 那么当前的最大价值就为到前一个物品为止的最大值(i+1)
for(int j = 0; j < maxCapacity; ++j)
recordArray[i][j] = recordArray[i+1][j];
// 当当前的背包容量 j >= 当前物品的重量时又存在两种情况
// 第一种是我们不放入当前物品 即此时的最大价值仍为到上一次物品的最大价值累计值
// 第二种是我们放入当前物品 那么到当前位置的最大价值应为到上一个物品为止的最大价值加上当前物品的价值
// 同时因为我们放入了当前物品 那么背包的容量就会相应的减少 当前物品 的重量
// 最后取这两种情况的最大值
for(int j = weight[i]; j <= capacity; ++j)
recordArray[i][j] = max(recordArray[i+1][j], recordArray[i+1][j-weight[i]] + value[i]);
}
// 单独对第一个物品来进行处理
// 背包容量放不下第一个物品时那么最大值就为到第二个物品为止的最大值
recordArray[0][capacity] = recordArray[1][capacity];
// 如果背包的容量大于第一个物品的重量
// 那么此时就存在放入第一个物品的可能性
// 所以我们应当来进行判断 放入与不放入的两种情况 取其最大值
if(capacity > weight[0])
recordArray[0][capacity] = max(recordArray[1][capacity], recordArray[1][capacity-weight[0]] + value[0]);
traceBack(recordArray, weight, capacity, quantity);
}
int main() {
vector<int> weight = {2, 2, 6, 5, 4};
vector<int> value = {6, 3, 5, 4, 6};
int capacity = 10;
int quantity = 5;
knapsack(weight, value, capacity, quantity);
}
// 解法二:
int main()
{
cin >> n >> v;
for (int i = 1; i <= n; i++)
cin >> c[i];//价值
for (int i = 1; i <= n; i++)
cin >> w[i];//体积
for (int i = 1; i <= n; i++)
f[i][0] = 0;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= v; j++)
if (j >= w[i])//背包容量够大
f[i][j] = max(f[i - 1][j - w[i]] + c[i], f[i - 1][j]);
else//背包容量不足
f[i][j] = f[i - 1][j];
cout << f[n][v] << endl;
return 0;
}
# Python
import numpy as np
import sys
def traceback(m, v, w, c, n):
x = np.zeros((1, n))[0] # 用于保存结果的数组
for i in range(1, n):
if m[i, c] == m[i+1, c]:
x[i-1] = 0
else:
x[i-1] = 1
c = c - w[i-1]
if m[n, c]:
x[n-1] = 1
print(x)
def knapsack(v, w, c, n):
m = np.zeros((n+1, c+1)) # 初始化 m ( n+1行 c+1列 ) 矩阵
# 末尾物品处理
jMax = min(w[n-1]-1, c)
for j in range(jMax+1):
m[n, j] = 0
for j in range(w[n-1], c+1):
m[n, j] = v[n-1]
# 中间物品处理
for i in range(n-1, 1, -1):
jMax = min(w[i-1]-1, c)
for j in range(jMax+1):
m[i, j] = m[i+1, j]
for j in range(w[i-1], c+1):
m[i, j] = max(m[i+1, j], m[i+1, j-w[i-1]]+v[i-1])
# 起始物品处理
m[1, c] = m[2, c]
if c >= w[0]:
m[1, c] = max(m[1, c], m[2, c-w[0]]+v[0])
traceback(m, v, w, c, n)
def main():
v = [6, 3, 5, 4, 6]
w = [2, 2, 6, 5, 4]
c = 10
n = 5
knapsack(v, w, c, n)
if __name__ == '__main__':
main()