0-1背包问题
给定n个物品,每个物品有一定的质量,记为
m
1
,
m
2
,
.
.
.
,
m
n
m_1,m_2,...,m_n
m1,m2,...,mn,同时每个物品又有一定的价值记为
v
1
,
v
2
,
.
.
.
,
v
3
v_1,v_2,...,v_3
v1,v2,...,v3。
现在给定一个背包,其最大能承受的质量为
m
a
x
W
e
i
g
h
t
maxWeight
maxWeight。
在不超过背包承重的情况下求出背包里面物品的价值的最大值。
使用数学语言描述就是:
求出序列
x
=
{
x
1
,
x
2
,
.
.
.
,
x
n
}
,
x
i
∈
{
0
,
1
}
s
.
t
.
∑
i
=
1
n
x
i
m
i
≤
m
a
x
W
e
i
g
h
t
m
a
x
V
a
l
u
e
s
=
m
a
x
∑
i
=
1
n
x
i
v
i
求出序列x=\{x_1,x_2,...,x_n\},x_i\in\{0,1\}\\ s.t. \sum_{i=1}^nx_im_i\le maxWeight\\ maxValues=max \sum_{i=1}^nx_iv_i
求出序列x={x1,x2,...,xn},xi∈{0,1}s.t.i=1∑nximi≤maxWeightmaxValues=maxi=1∑nxivi
求解思路
求解背包问题有很多的思路,例如贪心算法,回溯算法,遗传算法,以及本文中将要讨论的动态规划算法。
动态规划
- 直接求解问题有时很复杂,但是可以通过求解子问题间接求解最后的问题(和递归类似)
- 使用动态规划的时候,通常可以求出相应的递归表达式
分析思路
从特殊到一般
假设有如下的物品,其重量和价值如下表所示
物品编号 | 重量 | 价值 |
---|---|---|
1 | 4 | 2 |
2 | 3 | 4 |
3 | 2 | 3 |
4 | 3 | 4 |
5 | ? | 3 |
其中背包的最大容量为7,则背包里面物品最大的价值分析
分析
按照动态规划的思想,关键是需要将复杂问题分解为一个或者几个子问题,通过求解子问题,然后求解出最终的问题。
将问题进行划分
假设当前已经确认了前4个物品在背包容量为7的情况下物品的最大价值 m a x V a l u e [ 4 , 7 ] maxValue[4,7] maxValue[4,7],此时考虑第五个物品,究竟要不要将其装入背包,分为如下几种情况:
m a x V a l u e [ i , j ] maxValue[i,j] maxValue[i,j]表示在前i个物品在背包容量为j的情况下,背包里面物品的总价值
-
如果第五个物品的重量直接大于背包的容量,第五个物品肯定是装不进去的,此时前5个物品在背包容量为7的情况下物品的最大价值就是前4个物品在背包容量为7的情况下物品的最大价值,即 m a x V a l u e [ 5 , 7 ] = m a x V a l u e [ 4 , 7 ] maxValue[5,7]=maxValue[4,7] maxValue[5,7]=maxValue[4,7]
-
如果第五个物品的重量小于背包的容量,则第五个物品可能放进去
2.1 如果不放进去,此时背包里面物品的总价值为 m a x V a l u e [ 4 , 7 ] maxValue[4,7] maxValue[4,7];
2.2 如果将物品放进背包,此时物品的总价值应当为: m a x V a l u e [ 4 , 7 − 第五个物品的质量 ] + 第五个物品的价值 maxValue[4,7-第五个物品的质量]+第五个物品的价值 maxValue[4,7−第五个物品的质量]+第五个物品的价值
2.3 在上述两种情况下求出物品总价值的最大值即可
则接下来的任务就是求解 m a x V a l u e [ 4 , 7 − 第五个物品的质量 ] maxValue[4,7-第五个物品的质量] maxValue[4,7−第五个物品的质量],这样问题就变成了求解一个子问题
核心:写出递推表达式
已知:
给定n个物品,每个物品有一定的质量,记为
m
1
,
m
2
,
.
.
.
,
m
n
m_1,m_2,...,m_n
m1,m2,...,mn。
同时每个物品又有一定的价值记为
v
1
,
v
2
,
.
.
.
,
v
3
v_1,v_2,...,v_3
v1,v2,...,v3。
现在给定一个背包,其最大能承受的质量为
m
a
x
W
e
i
g
h
t
maxWeight
maxWeight。
则根据上面的分析,可以写出递归式如下:
根据递归式进行编码
void dynamicWithTwo()
{
//1. 接收用户数据的输入
int n, maxWeight;
printf("输入背包的容量和物品的个数,中间用空格区分\n");
scanf("%d%d", &maxWeight, &n);
int *weights = (int *)malloc(sizeof(int) * (n + 1)); // 物品质量数组,0号单元不使用
int *values = (int *)malloc(sizeof(int) * (n + 1)); // 物品价值数组
printf("输入数据格式:物品质量 价值\n");
for (int i = 1; i <= n; i++)
{
scanf("%d%d", &weights[i], &values[i]); // 接收物品质量
getchar();
}
//2. 处理数据
// 生成二维数组
int **maxValues = (int **)malloc(sizeof(int *) * (n + 1));
for (int i = 0; i <= n; i++)
{
maxValues[i] = (int *)malloc(sizeof(int) * (maxWeight + 1)); // 包含质量为0的情况
for (int j = 0; j <= maxWeight; j++)
{
maxValues[i][j] = 0;
}
}
// 开始填表
/*
maxValues[i][j]的含义为编号从[0,i]的物品当中,在背包容量为j的前提下的最大的价值
递推公式为:
*/
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= maxWeight; j++)
{
// 根据递推公式写出if-else分支
if (j >=weights[i])
{
maxValues[i][j] = max(maxValues[i - 1][j], maxValues[i - 1][j - weights[i]] + values[i]);
}
else
{
maxValues[i][j] = maxValues[i - 1][j];
}
}
}
// 打印输出最后得到的结果
printf("最后得到的结果为:\n");
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= maxWeight; j++)
{
printf("%d\t", maxValues[i][j]);
}
printf("\n");
}
}
测试结果: