一、背包九讲总述
关于动态规划问题,最典型的就是背包九讲,先理解背包九讲后再总结关于动态规划的问题。
1、01背包问题
2、完全背包问题
3、多重背包问题
4、混合背包问题
5、二维费用的背包问题
6、分组背包问题
7、背包问题求方案数
8、求背包问题的方案
9、有依赖的背包问题
二、01背包问题
01背包(ZeroOnePack): 有n件物品和一个容量为C的背包, 每种物品均只有一件,第i件物品的费用是w[i],价值是v[i]。求解将哪些物品装入背包可使价值总和最大。
*特点:*每种物品仅有一件,可以选择放或不放。
所以对于第i件物品,可以分为两种情况:放进背包与不放进背包
设V[i][j] 为只有i个物体,背包容量为j时,可获得的最大价值总和。
1、第i件选择不放进背包时:V[i][j]=V[i-1][j];
此时总价值就是只有i-1个物体,背包容量为j时,可获得的最大价值总和
2、第i件选择放进背包时:V[i][j]=V[i-1][j-w[i]]+v[i];
此时总价值就是只有i-1个物体,背包容量为j-w[i]时,可获得的最大价值总和,再加上自身的价值
我们需要的v[i][j]就是这两种情况的最大值
举例1:自上而下,从左到右生成
举例2:自下而上,从左到右生成
稍微解释一下:
1、e2这个单元格的意义是用来表示只有物品e时,有个承重为2的背包,那么这个背包的最大价值是0,因为e物品的重量是4,背包装不了。
2、对于d2单元格,表示只有物品e,d时,承重为2的背包,所能装入的最大价值,仍然是0,因为物品e,d都不是这个背包能装的。
3、对于承重为8的背包,a8=15,是怎么得出的呢?
根据01背包的状态转换方程,需要考察两个值,一个是V[i-1][j],对于这个例子来说就是b8的值9,另一个是V[i-1][j-w[i]]+v[i];而我们需要的是这两个值中的最大值。
其中:
V[i-1][j]表示我有一个承重为8的背包,当只有物品b,c,d,e四件可选时,这个背包能装入的最大价值,对于这个例子来说就是b8的值9。
V[i-1][j-w[i]]+v[i]表示若装入第i和物体,则背包剩下j-w[i]的空间,且只有b,c,d,e四件物体可以装,于这个例子来说就是b6的值9。 再加上自身价值6,9+6=15>b8=9,故a8==15;
我们用代码实现这个例子:
#include<iostream>
#include<algorithm>
using namespace std;
//全局变量定义在堆区,自动初始化
int V[100][100];
int x[100];
int packet(int n, int C, int v[], int w[])
{
int i = 0, j = 0;
//此循环为核心,重点!!
for (i = 0; i <= n; i++)
{
for (j = 1; j <= C; j++)
{
if (j < w[i])
{
V[i][j] = V[i - 1][j];
}
else
V[i][j] = max(V[i - 1][j ], V[i - 1][j - w[i]] + v[i]);
}
}
cout << V[n][C] << endl;
//判断那些物品被选中
for (i = n; i > 0; i--)
{
if (V[i - 1][C - w[i]] + v[i] >= V[i - 1][C])
{
x[i] = 1;
C -= w[i];
}
}
cout << "选中的物体是:";
for (i = 1; i <= n; i++)
{
if (x[i] == 1)
cout << i << " ";
}
cout << endl;
return 0;
}
int main()
{
int n; //输入的物品个数
int C; //最大的容量
int v[100] = { 0 }; //第i个物品的价值
int w[100] = { 0 }; //第i个物品的重量
cout << "输入物品个数" << endl;
cin >> n;
cout << "输入背包容量" << endl;
cin >> C;
cout << "输入各个物品的价值" << endl;
for (int i = 1; i <= n; i++)
{
cin >> v[i];
}
cout << "输入各个物品的重量" << endl;
for (int i = 1; i <= n; i++)
{
cin >> w[i];
}
packet(n, C, v, w);
while (1);
}
进一步升华:
上面使用二维数组来记录每一个状态,人们追求更好的方法,即使用滚动数组来优化空间,用一维数组来实现:
二维数组实现:
V[i][j] = max(V[i - 1][j ], V[i - 1][j - w[i]] + v[i])
一维数组实现:
V[j] = max(V[j], V[j - w[i]] + v[i])
注意是逆序实现(为了保证每个物体只有一件)
#include<iostream>
#include<algorithm>
using namespace std;
//全局变量定义在堆区,自动初始化
int V[100];
int x[100];
int packet(int n, int C, int v[], int w[])
{
int i = 0, j = 0;
//此循环为核心,重点!!
for (i = 0; i <= n; i++)
{
//滚动数组优化空间,逆序
for (j = C; j > 0; j--)
{
if (j < w[i])
{
V[j] = V[j];
}
else
V[j] = max(V[j], V[j - w[i]] + v[i]);
}
}
cout <<"最大价值为: "<< V[C] << endl;
cout << "被选中的物体为: ";
for (int k = n; k > 0; k--)
{
if (V[C - w[k]] + v[k] >= V[C])
{
x[k] = 1;
C -= w[k];
}
}
for (int k = 1; k <= n; k++)
{
if (x[k] == 1)
cout << k << " ";
}
return 0;
}
int main()
{
int n; //输入的物品个数
int C; //最大的容量
int v[100] = { 0 }; //第i个物品的价值
int w[100] = { 0 }; //第i个物品的重量
cout << "输入物品个数" << endl;
cin >> n;
cout << "输入背包容量" << endl;
cin >> C;
cout << "输入各个物品的价值" << endl;
for (int i = 1; i <= n; i++)
{
cin >> v[i];
}
cout << "输入各个物品的重量" << endl;
for (int i = 1; i <= n; i++)
{
cin >> w[i];
}
packet(n, C, v, w);
while (1);
}