问题描述:
给定n种物品和一背包。物品i的重量是,其价值为
,背包的容量为C。问应如何选择装入背包的物品,使得装入背包中物品的总价值最大?
输入:第一行有两个正整数n和W,n是物品种数,W是背包容量,接下来的一行中有n个正整数,表示物品的价值,第三行中有n个正整数,表示物品的重量。
5 10
6 3 5 4 6
2 2 6 5 4
输出样例:
15
1 1 0 0 1
问题分析:
- 首先定义子问题,dp[i][j] 代表在前 i个物品中挑选总重量不超过 W 的物品,每种物品至多只能挑选1个,使得总价值最大;这时的最优值记作 value[i][j],其中 1<=i<=n
- 对于一个子问题,状态转移方程可以列为:
dp[i][j] = { max{dp[i-1][j] , dp[i-1][j-w[i]+v[i] }j>w[i] 能装入i物品时,在装和不装中选择最大利益
dp[i][j] = {dp[i-1][j] , j<w[i] 第i件物品已经装不进去
例如:dp[2][4] ,第二件物品重量为2,能容下第二件物品,便开始比较dp[1][2]+v[2] ,dp[1][4],发现装入2时利益更高;
3.矩阵图可以表示为:
i \ W | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | 0 | 0 | 6 | 6 | 6 | 6 | 6 | 6 | 6 | 6 | 6 |
2 | 0 | 0 | 6 | 6 | 9 | 9 | 9 | 9 | 9 | 9 | 9 |
3 | 0 | 0 | 6 | 6 | 9 | 9 | 9 | 9 | 11 | 11 | 14 |
4 | 0 | 0 | 6 | 6 | 9 | 9 | 9 | 10 | 11 | 13 | 14 |
5 | 0 | 0 | 6 | 6 | 9 | 9 | 12 | 12 | 15 | 15 | 15 |
其初值定义为:当物品或者重量为0时,无法装入任何物品,则对应价值也为0;
4.求值最优解,自右下角开始,看一下当前价值与除去该物品(背包容量不变)的最大价值是否相等,如果相等说明没有装入该物品。依次倒着求解,并用book[]记录每一个物品是否被装入。
//矩阵大小,心里有个概念,不要直接写成n*n
#include<stdio.h>
#include<stdlib.h>
void maxValue(int n,int W,int *value,int *weight){
int arr[n+1][W+1];
for(int i=0;i<=n;i++){ //边缘初始化
arr[i][0]=0;
}
for(int j=0;j<=W;j++){
arr[0][j]=0;
}
for(int i=1;i<=n;i++){
for(int j=1;j<=W;j++){
if(j<weight[i]){ //装不上
arr[i][j]=arr[i-1][j];
}
else{
if(value[i]+arr[i-1][j-weight[i]]<arr[i-1][j]){//能装但不如不装
arr[i][j]=arr[i-1][j];
}
else{
arr[i][j]=arr[i-1][j-weight[i]]+value[i]; //装上价值更高
}
}
}
}
for(int i=0;i<=n;i++){
for(int j=0;j<=W;j++){
printf("%4d",arr[i][j]);
}
printf("\n");
}
int j=W;;
int n1=n;
int book[n+1];
for(int i=n;i>=1;i--){
if(arr[i][j]==arr[i-1][j]){ //来自上边
book[n1]=0;
n1--;
}
else{ //来自左上
book[n1]=1;
n1--;
j-=weight[i];
}
}
printf("%d\n",arr[n][W]); //最大价值
for(int i=1;i<=n;i++){ //选择的商品
printf("%d ",book[i]);
}
}
int main(){
int n,w;
scanf("%d%d",&n,&w);
int *value = (int *)malloc(sizeof(int)*(n+1));
int *weight = (int *)malloc(sizeof(int)*(n+1));
for(int i=1;i<=n;i++){
scanf("%d",&value[i]);
}
for(int i=1;i<=n;i++){
scanf("%d",&weight[i]);
}
maxValue(n,w,value,weight);
}
空间复杂度优化:O(n)仅求取最优值
当然上述问题使用了O(mn)的空间复杂度,因为我们使用了二维数组记录每种物品i 和每种重量w的情况。我们对此可以进行优化,利用一维数组解决问题:
其实质是从第二行开始对第一行进行压缩,重复利用一维数组记录最优值,缺点:因为压缩了数据,所以找不到最优解(物品选取)。
以 w[]={2,1,3,2}
v[]={12,10,20,15}为例:
在一维数组arr[6]里,先记录加入第一件物品的最大价值,然后引入第二件物品,其计算结果的值直接覆盖第一行(即实现了O(n)空间复杂度。
//O(n)空间复杂度的问题解法
#include<stdio.h>
#include<string.h>
void backpack(int *w,int *v,int m,int n){
int dp[n+1];
for(int i=0;i<=n;i++){ //容量0~5,是否能容下第一件物品
dp[i] = w[0]<=i?v[0]:0;
}
printf("第1件物品加入:");
for(int i=0;i<=m;i++){
printf("%4d",dp[i]);
}
printf("\n");
for(int i=1;i<n;i++){ //更新0~5的容量背包
for(int j=m;j>=w[i];j--){
dp[j] = dp[j] > v[i]+dp[j-w[i]]?dp[j]:v[i]+dp[j-w[i]];
}
printf("第%d件物品加入:",i+1);
for(int i=0;i<=m;i++){
printf("%4d",dp[i]);
}
printf("\n");
}
}
int main(){
int w[]={2,1,3,2};
int v[]={12,10,20,15};
backpack(w,v,5,4); //5背包容量,4个物品
}
背包问题可以解决的一些问题:
1.集合划分问题。
问题描述:设A={a1,a2,…an} 是正整数的集合,试设计一个算法判断是否能将A划分为两个子集A1和A2,使得A1中的数的和与A2中的数的和相等。
输入:第一行有1个正整数n,接下来的一行中有n个正整数,表示a1,a2,…an。
输出:如果可以划分为两个子集,则输出“yes”,否则输出“No”
输入样例:
5
6 3 5 4 2
输出样例:
Yes
分析:问题可以转化为,给定元素个数为n的数组a[],数组元素的和为sum,对应于两个背包问题,即有n 个物品,每个物品的重量和价值均为为a[i],每个背包容量为sum/2,求解背包中的物品最大价值。
物品重量很容易跟时间、加和等要素结合,但在背包问题中物品价值是要求取的数值,因此,让物品价值跟重量均为a[i],可以保证背包尽量装满的同时就是最大值。
回到具体问题,必须找到能够满足sum/2的装满的方法,否则不满足要求。只要比较dp[n]是否等于sum/2即可。
#include<stdio.h>
void package(int *a,int n,int w){
int dp[n+1][w+1];
for(int i=1;i<=n;++i){
dp[i][0]=0;
}
for(int j=0;j<=w;++j){
dp[0][j]=0;
}
for(int i=1;i<=n;i++){
for(int j=1;j<=w;j++){
if(a[i]>j){
dp[i][j]=dp[i-1][j];
}
else{
if(dp[i-1][j]>dp[i-1][j-a[i]]+a[i]){
dp[i][j]=dp[i-1][j];
}
else{
dp[i][j]=dp[i-1][j-a[i]]+a[i];
}
}
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=w;j++){
printf("%6d",dp[i][j]);
}
printf("\n");
}
if(dp[n][w]==w){
printf("Yes\n");
}
else
printf("No\n");
}
void division(int *a,int n,int sum){
int child=sum/2;
if(child%2!=0){ //加和不是偶数必然分不开
printf("No\n");
return;
}
else{
package(a,n,child);
}
}
int main(){
int n;
scanf("%d",&n);
int a[n+1];
int sum=0;
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
sum+=a[i];
}
division(a,n,sum);
}