01背包问题的一种动态规划解法
问题描述
给定一个背包,容量(如负重等)为m
给定一组物品共
n
n
n 个,每种一个,各有价值和重量
1 | 2 | 3 | … \dots … | n | |
---|---|---|---|---|---|
v[i] | v[1] | v[2] | v[3] | … \dots … | v[n] |
w[i] | w[1] | w[2] | w[3] | … \dots … | w[n] |
求能放入背包的最大价值
思路
动态规划,实为穷举放入背包各种物品的情况,根据规模更小的子问题递推出更大规模问题的解
建表 dp[N][N]
表中值 dp[i][j]
表示将给定的前 i
件物品放入一个容量为 j
的背包,所能放入的最大价值
(j=)0 | 1 | 2 | 3 | 4 | |
---|---|---|---|---|---|
(i=)0 | 0 | 0 | 0 | 0 | 0 |
1 | 0 | dp[1][1] | |||
2 | 0 | … \dots … | |||
3 | 0 | … \dots … | |||
4 | 0 | dp[4][4] |
递推式
d p [ i ] [ j ] = { d p [ i − 1 ] [ j ] , w [ i ] > j m a x ( d p [ i − 1 ] [ j ] , d p [ i − 1 ] [ j − w [ i ] ] + v [ i ] ) , w [ i ] < = j dp[i][j]= \begin{cases} dp[i-1][j],\quad w[i]>j\\ max(dp[i-1][j], dp[i-1][j-w[i]]+v[i]), \quad w[i]<=j \end{cases} dp[i][j]={dp[i−1][j],w[i]>jmax(dp[i−1][j],dp[i−1][j−w[i]]+v[i]),w[i]<=j
d
p
[
i
]
[
j
]
=
d
p
[
i
−
1
]
[
j
]
dp[i][j]=dp[i-1][j]
dp[i][j]=dp[i−1][j] 当前背包总容量 j
不能容纳第i
件物品(质量为w[i]
),放不进去不必考虑,等同于i-1
件物品的情况
d
p
[
i
]
[
j
]
=
m
a
x
(
d
p
[
i
−
1
]
[
j
]
,
d
p
[
i
−
1
]
[
j
−
w
[
i
]
]
+
v
[
i
]
)
dp[i][j]=max(dp[i-1][j], dp[i-1][j-w[i]]+v[i])
dp[i][j]=max(dp[i−1][j],dp[i−1][j−w[i]]+v[i]) 当前背包总容量 j
能容纳第i
件物品(质量为w[i]
)考虑第i
件物品放入和不放入背包两种情况的最优解。
不放入,同i-1
件物品的情况;
放入,要保证第i
件物品可以放入背包中,即先放入第i
件物品。
在放入第i
件物品后,当前背包的总容量为j-w[i]
,此时的最优解变为前 i-1
件物品放入一个容量为 j-w[i]
的背包,所能放入的最大价值 dp[i-1][j-w[i]]
与第i
件物品的价值 v[i]
之和。
代码
//01背包问题
//n件物品各有价值和质量,每种1件,求一个容量有限的背包装入的最大价值
#include <iostream>
using namespace std;
const int N = 1024;//
int dp[N][N];//容量为j的背包放入前i件物品时的最大价值dp[i][j]
int v[N];//价值
int w[N];//质量
void maketable(int m, int n)//n件物品,背包总容量为m
{
for (int i = 0; i <= n; i++) dp[0][i] = 0;
for (int i = 0; i <= m; i++) dp[i][0] = 0;
//容量或装入的物品数目为0时价值为0
for (int i = 1; i <= n; i++)
{
for (int j = m; j > 0; j--)
{
if (w[i] > j) dp[i][j] = dp[i - 1][j];
//放不下,不放,和少一件物品等值
//dp[i][j]=dp[i-1][j],w[i]>j
else
{
int tmp = dp[i - 1][j - w[i]] + v[i];
//在保证放入背包(i-1)件物品后,剩余空间足够放下第i件物品时的最大价值
//+第i件物品的价值
//确定放于不放第i件物品时,容量为m物品为i件的子问题最优解
//即放于不放第i件物品时,容量为m的背包对于i件物品,所能装入的最大价值
//即max(dp[i-1][j], dp[i-1][j-w[i]]+w[i])
if (dp[i - 1][j] < tmp)
dp[i][j] = tmp;
else
dp[i][j] = dp[i - 1][j];
}
}
}
}
int main()
{
int m, n;
cout << "输入背包容量:";
cin >> m;
cout << "输入物品总数:";
cin >> n;
cout << "输入物品价值";
for (int i = 1; i <= n;i++) cin >> v[i];
cout << "输入物品质量";
for (int i = 1; i <= n;i++) cin >> w[i];
maketable(m, n);
cout << "最大价值为:" << dp[n][m];
return 0;
}
空间优化
用一维数组存储i-1
件物品时各容量的最优解
注意此时内循环反向,防止前一状态的数据被提前覆盖
例:
(j=)0 | 1 | 2 | … \dots … | m | |
---|---|---|---|---|---|
(i=)0 | |||||
… \dots … | … \dots … | … \dots … | … \dots … | … \dots … | … \dots … |
i-1 | 0 | 1 i − 1 1_{i-1} 1i−1 | 2 i − 1 2_{i-1} 2i−1 | … \dots … | m i − 1 m_{i-1} mi−1 |
i | 0 | 1 i 1_i 1i | 2 i 2_i 2i | … \dots … | m i m_i mi |
… \dots … | … \dots … | … \dots … | … \dots … | … \dots … | … \dots … |
如上表,当dp[i-1][j] != dp[i][j]
即
j
i
−
1
≠
j
i
j_{i-1} \ne j_{i}
ji−1=ji 时
(j=)0 | … \dots … | j | … \dots … | m | |
---|---|---|---|---|---|
… \dots … | … \dots … | … \dots … | … \dots … | … \dots … | … \dots … |
第i-1 次循环 | 0 | … \dots … | j i − 1 j_{i-1} ji−1 | … \dots … | m i − 1 m_{i-1} mi−1 |
第i 次循环 | 0 | … \dots … | j i ≠ j i − 1 j_i\ne j_{i-1} ji=ji−1 | … \dots … | m i = j i + v [ i ] j i ≠ d p [ i − 1 ] [ j ] m i ≠ d p [ i − 1 ] [ j ] + v [ i ] m_i=j_i+v[i]\\j_i\ne dp[i-1][j]\\m_i\ne dp[i-1][j]+v[i] mi=ji+v[i]ji=dp[i−1][j]mi=dp[i−1][j]+v[i] |
若j=m-w[i]
且dp[i][m]=dp[1-1][j]+v[i]
时,
因正序循环,不能保证一维数组的dp[j]=dp[i-1][j]
,
即正序循环在j
较小时的值dp[j]
改变,i-1
次循环的状态 dp[i-1][j]
被覆盖
要保证i-1
次循环的状态 dp[i-1][j]
在需要使用时不被覆盖,需要内循环反向,即先使用再更新
//内循环反向,确保每次考虑下一件物品时在前一状态的基础上,即dp[j-w[i]]=dp[i-1][j-w[j]]
//若内循环正向,会导致,第二次循环时,dp[j-w[i]]=dp[i][j-w[i]],即上一状态的数据被覆盖
const int N = 1024;//
int dp[N];//容量为j的背包放入前i件物品时的最大价值dp[j],i由第i次循环表示
int v[N];//价值
int w[N];//质量
void maketable(int m, int n)//n件物品,背包总容量为m
{
for (int i = 0; i <= n; i++) dp[i] = 0;
//容量或装入的物品数目为0时价值为0
for (int i = 1; i <= n; i++)
{
for (int j = m; j > 0; j--)
//内循环反向,确保每次考虑下一件物品时在前一状态的基础上,即dp[j-w[i]]=dp[i-1][j-w[j]]
//若内循环正向,会导致,第二次循环时,dp[j-w[i]]=dp[i][j-w[i]],即上一状态的数据被覆盖
{
if (w[i] > j) ;
//放不下,不放,和少一件物品等值
//dp[i][j]=dp[i-1][j],w[i]>j
//与上一状态一致,即保持dp[j]不变
else
{
int tmp = dp[j - w[i]] + v[i];
//在保证放入背包(i-1)件物品后,剩余空间足够放下第i件物品时的最大价值
//+第i件物品的价值
//确定放于不放第i件物品时,容量为m物品为i件的子问题最优解
//即放于不放第i件物品时,容量为m的背包对于i件物品,所能装入的最大价值
//即max(dp[i-1][j], dp[i-1][j-w[i]]+w[i])
if (dp[j] < tmp)
dp[j] = tmp;
else
;
}
}
}
}
如有不足,还请指正。