问题描述
有编号分别为a,b,c,d,e的五件物品,它们的重量分别是2,2,6,5,4,它们的价值分别是6,3,5,4,6,每件物品数量只有一个,现在给你个承重为10的背包,背包里装入的物品具有最大的价值总和是多少?具有最大价值时,分别是哪些物品在包里?
思路分析
状态转移方程为:
设背包总容量为C,j为当前背包可用容量,i为前i个物品,第i个物品价值为Vi,第i个物品重量Wi。
F[i,j]表示当背包可用容量为j时,在前i个物品中,背包能装下的最大价值,有方程:
F[i,j] = max { F[i−1,j], F[i−1,j−Wi]+Vi } (F[i−1,j] 表示不拿,F[i−1,j−Wi]+Vi 表示拿)
表格为:
i | name | weight | value | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
4 | a | 2 | 6 | 0 | 0 | 6 | 6 | 9 | 9 | 12 | 12 | 15 | 15 | 15 |
3 | b | 2 | 3 | 0 | 0 | 3 | 3 | 6 | 6 | 9 | 9 | 9 | 10 | 11 |
2 | c | 6 | 5 | 0 | 0 | 0 | 0 | 6 | 6 | 6 | 6 | 6 | 10 | 11 |
1 | d | 5 | 4 | 0 | 0 | 0 | 0 | 6 | 6 | 6 | 6 | 6 | 10 | 10 |
0 | e | 4 | 6 | 0 | 0 | 0 | 0 | 6 | 6 | 6 | 6 | 6 | 6 | 6 |
使用一个一维数组res[n],如果物品i被拿,则res[i] = 1,判断是否被拿的条件如下:
F[i,j]==F[i-1,j-W[i]+V[i]]
我的实现1
/*
* W:物品重量数组
* V:物品价值数组
* res:结果输出,当获取最大重量时,分别是哪几个物品在包中
* n:物品数量
* C:背包总容量
* 返回值:背包的最大价值
*/
int Package01(int *W, int *V, int *res, int n, int C)
{
//表格
int** F = new int*[n];
for (int i = 0; i < n; i++)
{
F[i] = new int[C + 1];
}
//初始化表格
for (int i = 0; i < n; i++)
{
for (int j = 0; j <= C; j++)
F[i][j] = 0;
}
//第1列,表示背包容量为0时,所以第1列都为0
//初始化第1行,当背包容量小于第1个物品重量时,背包价值为0,否则为背包价值为第1个物品价值
for (int i = 1; i <= C; i++)
{
F[0][i] = (i < W[0]) ? 0 : V[0];
}
//i表示物品下标
for (int i = 1; i < n; i++)
{
//j表示背包容量
for (int j = 1; j <= C; j++)
{
if (j < W[i])
{
F[i][j] = F[i - 1][j];
}
else
{
//状态转移方程
F[i][j] = (F[i - 1][j]>F[i - 1][j - W[i]] + V[i]) ? F[i - 1][j] : F[i - 1][j - W[i]] + V[i];
}
}
}
int maxValue = F[n - 1][C];
//下面计算分别都是哪些物品在包中
int i = n - 1;
int j = C;
while (i)
{
//bool test = (F[i][j] == F[i - 1][j - W[i]] + V[i]);
if (F[i][j] == F[i - 1][j - W[i]] + V[i])
{
res[i] = 1;
j = j - W[i];
}
i--;
}
if (F[0][j])
res[0] = 1;
for (int i = 0; i < n; i++)
{
for (int j = 0; j <= C; j++)
cout << F[i][j] << " ";
cout << endl;
}
//清理动态分配内存
for (int i = 0; i < n; i++)
{
delete[] F[i];
F[i] = nullptr;
}
delete[] F;
F = nullptr;
return maxValue;
}
//处理输入输出
//输入:
//第1行:物品个数n,背包总容量C
//第i(i>=2)行到第(i+n-1)行:物品重量Wi,物品价值Vi
int main()
{
int n = 0;
int C = 0;
cin >> n >> C;
if (n <= 0 || C <= 0)
return 0;
int* W = new int[n];
int* V = new int[n];
for (int i = 0; i < n; i++)
{
cin >> W[i] >> V[i];
}
int* res = new int[n];
for (int i = 0; i < n; i++)
res[i] = 0;
int maxValue = Package01(W, V, res, n, C);
for (int i = 0; i < n; i++)
{
cout << res[i] << " ";
}
cout << endl;
cout << maxValue << endl;
delete[] W;
W = nullptr;
delete[] V;
V = nullptr;
delete[] res;
res = nullptr;
system("pause");
return 0;
}
/*
输入为:
5 10 //5种物品,背包承重为10
2 6 //第一种物品,重量为2,价值为1,数目1个
2 3
6 5
5 4
4 6
输出为:
0 0 6 6 6 6 6 6 6 6 6
0 0 6 6 9 9 9 9 9 9 9
0 0 6 6 9 9 9 9 11 11 14
0 0 6 6 9 9 9 10 11 13 14
0 0 6 6 9 9 12 12 15 15 15
1 1 0 0 1
15
*/
优化
因为在考虑前i个问题时,只与i-1的结果相关,可以使用一维数组来存放结果,又因为F[i,j]需要参考F[i-1,j]和F[i-1,j-Wi]的值,我们可以通过从右到左的遍历,避免数据被覆盖。因此有:
F[j] = max { F[j], F[j−Wi]+Vi } j初始化为背包总容量C
在这种情况,求哪些物品在包中的方法就不可用了,新方法为,使用一二维数组path[][],当物品计算某一F[j]时,如果物品i被拿了,则标记path[i,j]。但这样貌似又是O(nC)的空间了。:(,如果不用求路径的情况下这种方法更好吧。
我的实现2
int Package01(int *W, int *V, int**path, int n, int C)
{
int* F = new int[C + 1];
//初始化第0个物品,即第1行
for (int j = 0; j <= C; j++)
{
if (j < W[0])
F[j] = 0;
else
{
F[j] = V[0];
path[0][j] = 1;
}
}
//从物品1开始遍历,即第2行
for (int i = 1; i < n; i++)
{
for (int j = 0; j <= C; j++)
cout << F[j] <<" ";
cout << endl;
//逆序遍历
for (int j = C; j >= 0; j--)
{
if (j >= W[i] && F[j - W[i]] + V[i] > F[j])
{
F[j] = F[j - W[i]] + V[i];
path[i][j] = 1;
}
}
}
//打印最后一行
for (int j = 0; j <= C; j++)
cout << F[j] << " ";
cout << endl;
int maxValue = F[C];
delete[] F;
return maxValue;
}
int main()
{
int n = 0;
int C = 0;
cin >> n >> C;
if (n <= 0 || C <= 0)
return 0;
int* W = new int[n];
int* V = new int[n];
for (int i = 0; i < n; i++)
{
cin >> W[i] >> V[i];
}
int** path = new int*[n];
for (int i = 0; i < n; i++)
path[i] = new int[C + 1];
for (int i = 0; i < n; i++)
{
for (int j = 0; j <= C; j++)
path[i][j] = 0;
}
int maxValue = Package01(W, V, path, n, C);
cout << maxValue << endl;
//输出路径
int i = n - 1;
int j = C;
while (i >= 0 && j >= 0)
{
if (path[i][j] == 1)
{
cout << i << " ";
j -= W[i];
}
i--;
}
cout << endl;
delete[] W;
W = nullptr;
delete[] V;
V = nullptr;
for (int i = 0; i < n; i++)
{
delete[] path[i];
path[i] = nullptr;
}
delete[] path;
path = nullptr;
return 0;
}
/*
输入:
5 10
2 6
2 3
6 5
5 4
4 6
输出:
0 0 6 6 6 6 6 6 6 6 6
0 0 6 6 9 9 9 9 9 9 9
0 0 6 6 9 9 9 9 11 11 14
0 0 6 6 9 9 9 10 11 13 14
0 0 6 6 9 9 12 12 15 15 15
15
4 1 0
*/
包装满的最优解
如果要求是,包必须装满,那么可以将F方程除F[i][0]
之外的格子初始化为负无穷(表示为不合法输出,下面用-%表示),因此有以下矩阵:
i | name | weight | value | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
4 | a | 2 | 6 | 0 | -% | 6 | -% | 9 | 4 | 12 | 10 | 15 | 13 | 14 |
3 | b | 2 | 3 | 0 | -% | 3 | -% | 6 | 4 | 9 | 7 | 8 | 10 | 11 |
2 | c | 6 | 5 | 0 | -% | -% | -% | 6 | 4 | 5 | -% | -% | 10 | 11 |
1 | d | 5 | 4 | 0 | -% | -% | -% | 6 | 4 | -% | -% | -% | 10 | -% |
0 | e | 4 | 6 | 0 | -% | -% | -% | 6 | -% | -% | -% | -% | -% | -% |