本文仅代表我个人查阅资料后,对于背包问题的理解,文中如有不好或者不对的地方欢迎指正。
1.0-1背包问题
1.1 问题描述
给定N个物品,每个物品有一个重量W和一个价值V.你有一个能装M重量的背包.问怎么装使得所装价值最大.每个物品只有一个.
1.2问题分析
从这个问题中是给定了一个容量没M的背包,和N个重量为W和价值为V的物品,我要用这N个物品是填充背包使,填充的价值最大。这里每个物品都有两种状态,放进背包和不放入背包。
对于这个问题这里将使用动态规划去做。动态规划的重点也就是找出递推公式。假设我们给出N = 3 , M= 5 ,三个物品的重量和价值分别为:
物品 | 重量 | 价值 |
---|---|---|
a | 2 | 3 |
b | 3 | 5 |
c | 4 | 7 |
我们这里可以假设只有一个物品a那么我们要装满背包的最大价值就是3 。若果有两个物品a,b那么就会有(a,b) ,a , b这三个可能情况,细心观察前面三种情况我们可以把它分为两类即 :包含b和不包含b 。若果我们再把背包的容量J考虑进来就是 :当背包容量为J时背包中包含b的价值和当背包容量为J时背包中包含b的价值。依次类推可以得到同样的结论。由此我们可以定义一个数组dp[i][j] .dp[i][j]表示背包容量为j并且有i件物品时的最大价值。那么最大价值就是从这两个里面取最大值,即dp[i][j] = max(dp[i-1][j] , dp[i][j -W[I]] +v[I]) .这样我们就有了递推公式。
代码如下:
#include<iostream>
#include<cstring>
using namespace std ;
int main()
{
int N ; //物品的件数
int M ; //能装的物品的总重
cin >> N >>M ;
int V[N+1] ; //每件物品的价值
int W[N+1] ; //每件物品的重量
for(int i = 1 ; i <= N ; i++)
cin >> W[i] >>V[i] ;
int dp1[N+1][M+1] ; //dp1[i][j] 表示i件物品达到j的重量的最大价值
memset(dp1 , 0 ,sizeof(dp1)) ;
for(int i = 1 ; i <= N ; i++)
{
for(int j = 1 ; j <= M ; j++)
{
if(W[i] <= j)
dp1[i][j] = dp1[i-1][j] >dp1[i-1][j-W[i]]+V[i] ? dp1[i-1][j] : dp1[i-1][j-W[i]] +V[i] ;
else
dp[i][j] = dp[i-1][j] ;
}
}
cout << dp1[N][M] <<endl ;
return 0 ;
}
1.3代码的优化
对于上面的代码我们分析可以得到程序的 ,时间复杂度为O(NM) ,空间复杂度为O(NM) ,在这里时间复杂度我们不能再优化了,但是对于空间复杂度我们可以在进行优化。
在这里我们可以使用一个一维数组去代替二维数组dp,但是我们发现上面的代码dp1[i][j] = dp1[i-1][j] >dp1[i-1][j-W[i]]+V[i] ? dp1[i-1][j] : dp1[i-1][j-W[i]] +V[i] ;
中使用数组中前面的值来更新后面的值,因此要解决这个问题我们可以让第二个for循环逆序遍历这样,当我们求解后面的值时,前面的值还没有被替换掉。代码如下:
#include<iostream>
#include<cstring>
using namespace std ;
int main()
{
int N ; //物品的件数
int M ; //背包能装的物品的总重
cin >> N >>M ;
int V[N+1] ; //每件物品的价值
int W[N+1] ; //每件物品的重量
for(int i = 1 ; i <= N ; i++)
cin >> W[i] >>V[i] ;
int dp1[M+1] ; //用一维数组代替dp1[i][j] 表示i件物品达到j的重量的最大价值
memset(dp1 , 0 ,sizeof(dp1)) ;
for(int i = 1 ; i <= N ; i++)
{
for(int j = M ; j >= 1 ; j--) //逆序遍历,以达到前面的值更新后面的值
{
if(W[i] <= j)
dp1[j] = dp1[j] >dp1[j-W[i]]+V[i] ? dp1[j] : dp1[j-W[i]] +V[i] ;
}
}
//总的执行次数为 N+3*N*M ,时间复杂度为O(N*M) ,空间复杂度为O(M) 空间复杂度降低了
cout << dp1[M] <<endl ;
return 0 ;
}
进过优化后程序的 时间复杂度为O(N*M) ,空间复杂度为O(M) 空间复杂度降低了。
2 完全背包问题
2.1 问题描述
完全背包问题和0-1背包问题相似,区别在于完全背包问题他的每件物品的数量是无限的我们可以无限取。
2.2问题分析
对于这个问题我们可以从0-1背包入手,可以看到区别在于每种物品可以无限取,但是实际上每种物品能取得数量是有限的那就是,背包的总容量/每个物品的质量 ,即M/W[i] .那么也就是说每种物品有M/W[i]+1种取法 即 0 , 1 ,2 ,3… M/W[i] 。就直接的方法就是在0-1背包中加一个for循环去遍历看看第i个物品放几个 。代码如下
#include<iostream>
#include<cstring>
using namespace std ;
int max(int a , int b)
{
return a>b? a : b ;
}
int main()
{
int N ; //物品的件数
int M ; //背包能装的物品的总重
cin >> N >>M ;
int V[N+1] ; //每件物品的价值
int W[N+1] ; //每件物品的重量
for(int i = 1 ; i <= N ; i++)
cin >> W[i] >>V[i] ;
int dp1[N+1][M+1] ; //dp1[i][j] 表示i件物品达到j的重量的最大价值
memset(dp1 , 0 ,sizeof(dp1)) ;
for(int i = 1 ; i <= N ; i++) //需执行N次
{
for(int j = 1 ; j <= M ; j++) //需执行N*M次
{
int K = j/W[i] ;
for(int L = 0 ; L <= K ; L++) //L表示第i个物品取得件数
dp1[i][j] = max(dp1[i][j] , dp1[i-1][j - L*W[i]] + L*V[i]);
}
}
//总的执行次数为 N+3*N*M ,时间复杂度为O(N^3) ,空间复杂度为O(N*M)
cout << dp1[N][M] <<endl ;
return 0 ;
}
2.3 代码的优化
运行上面的代码我们是可以得到最优解的,但是我们仔细分析代码可以发现,这个代码的时间复杂度为O(n^3) ,空间复杂度为O(n^2)。这里我们可以考虑和0-1背包一样可以进行空间复杂度的优化。对于时间复杂度的优化可以是有i件物品时,容量为j的背包所能达到的最大价值中,包含i的件数可能为0,1……k*W[i] <M 。所以代码如下
#include<iostream>
#include<cstring>
using namespace std ;
int max(int a , int b)
{
return a>b? a : b ;
}
int main()
{
int N ; //物品的件数
int M ; //能装的物品的总重
cin >> N >>M ;
int V[N+1] ; //每件物品的价值
int W[N+1] ; //每件物品的重量
//const int max = 0X3f ;
for(int i = 1 ; i <= N ; i++)
cin >> W[i] >>V[i] ;
int dp1[M+1] ; //dp1[i][j] 表示i件物品达到j的重量的最大价值
memset(dp1 , 0 ,sizeof(dp1)) ;
for(int i = 1 ; i <= N ; i++)
{
for(int j = W[i] ; j <= M ; j++)
{
dp1[j] = max(dp1[j] , dp1[j - W[i]] + V[i]);
}
}
cout << dp1[M] <<endl ;
return 0 ;
}
进过上面的优化我们可以看到,程序的时间复杂度为O(n^2) ,空间复杂度为O(n)。