动态规划:0-1背包问题

问题描述:

   给定n种物品和一背包。物品i的重量是W_i{},其价值为V_{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

问题分析:

  1. 首先定义子问题,dp[i][j] 代表在前 i个物品中挑选总重量不超过 W 的物品,每种物品至多只能挑选1个,使得总价值最大;这时的最优值记作 value[i][j],其中 1<=i<=n
  2. 对于一个子问题,状态转移方程可以列为:

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 \  W012345678910
00     0000000000
100666666666
200669999999
300669999111114
4006699910111314
50066991212151515

其初值定义为:当物品或者重量为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);
	
}

2.双机调度问题:

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值