0-1背包问题

0-1背包问题描述如下:

有一个背包总容量为sum ,现在有N个物品准备放入背包,每个物品都有重量W[i]和该物品的价值V[i]  , 但是考虑到背包的容量只有sum,所以需要进行选择性的放入,

从这N个物品中选取部分或者全部,使得最后背包中物品的价值value达到最大值。

由于问题的特殊性,没一件物品的选择只有两种情况。也就是装入或者不装入,不会有装入部分或者重复装入的情况发生。

X[i]数组记录物品 i 的装入活着不装如,装入为1 , 不装如为 0 ,也就是0-1背包问题是一个特殊的整数规划问题。


最有子结构性质:


0-1背包问题具有最有子结构性质。我们使用c[i][j] 表示在背包容量为j的时候前i个物品的装包情况,

比如我们的最优解为(x[1],x[2],x[3],x[4]......x[n] ), x[i] = 0 或者 1 . 即为c[n][sum]的值为最有情况下取得的最大值

当我们最后一个物品n没有选择,也就是x[n]=0 , 此时c[n][sum] = c[n-1][sum] .

当我们最后一个物品选择的时候,也就是 x[n]=1 , 此时c[n][sum] = c[n-1][sum - 第1个物品的重量] + 第i个物品的价值 .

对于c[i][j]来说,其转移状态方程为 :c[i][j]=max{c[i-1][j],c[i-1][j-w[i]]+v[i]}

上面的方程也就是说:对于前 i 物品装入j容量的背包中的选择情况,如果只有考虑当前第i号物品的选择情况,有两种情况:

(1)当前i号物品不装入,也就是 "前 i 种物品装包j 容量的选择情况"  转化成 "前i-1 种物品装包j容量的选择情况"

(2)当前i号物品装入,则问题转化为“前i-1件物品放入剩下的容量为j-w[i]的背包中”(此时能获得的最大价值就是c [i-1][j-w[i]]再加上通过放入第i件物品获得的价值v[i])。则c[i][j]的值就是1、2中最大的那个值。


问题求解:

举个列子,如下:


边界控制:首先计算过程为先横向,再纵向。也就是开始对于选择物品从0~n的判断情况。对于每次增加一种物品的时候,背包容量都是从0开始计算

由于物品为0的时候,也就是上面图中的第一行,此时物品没有选择,所以背包从0~10的时候都没有物品选择,所以价值都为0

接下来第二行开始判断,此时可选物品有一号物品,此时背包容量从0~sum,开始判断

为什么要这样开始判断呢? 对于动态规划来说,每一种最优解都包含子问题的最优解问题,而且需要记录下每一个子问题的最优解,

可以从子问题的最优解中推断出最后问题的最优解结果

c[i][j]=max{c[i-1][j],c[i-1][j-w[i]]+v[i]}


// 动态规划法求解最大值
void Package(int sum , int n , int w[] , int v[] , int c[][100]) {
	int i , j;
	// 第一排,背包容量为0,所以c[0][j]赋值为0,
	for(j=0 ; j<=sum ; j++) {
		c[0][j] = 0;
		printf("%3d" , c[0][j]);
	}
	printf("\n");
	// 背包容量从1~sum,物品一个一个判断是否放入
	for(i=1 ; i<=n ; i++) {	
		for(j=0 ; j<=sum ; j++) {
			if(j >= w[i]) {
				c[i][j] = MaxValue(c[i-1][j] , c[i-1][j-w[i]]+v[i]);
			} else {  
				c[i][j] = c[i-1][j];
			}
			printf("%3d" , c[i][j]);
		}
		printf("\n");
	}
}

对于求解出最优情况下的最大值之后,我们如果还需要找出具体的物品选择情况

则需要通过判断才行,可以通过c[i][j] 是否等于 c[i-1][j] 判断 ,如下:

// 倒退求解哪几个物品放入
void SearchTrace(int sum , int n , int w[] , int x[] , int c[][100]) {
	int i = n;
	while(i) {
		// 第i个物品没有放入
		if(c[i][sum] == c[i-1][sum]) {
			x[i] = 0;	
		} else { // 第i个物品放入
			x[i] = 1;
			sum -= w[i]; 
		}
		i--;
	}
}

总结

01背包问题是最基本的背包问题,它包含了背包问题中设计状态、方程的最基本思想,另外,别的类型的背包问题往往也可以转换成01背包问题求解。故一定要仔细体会上面基本思路的得出方法,状态转移方程的意义,以及最后怎样优化的空间复杂度。


代码如下:

/* 0-1背包问题
** 动态规划法
** 时间复杂度O(n*sum)
*/
#include<stdio.h>
// 比较大小
int MaxValue(int a , int b) {
	return a>=b?a:b;
}
// 动态规划法求解最大值
void Package(int sum , int n , int w[] , int v[] , int c[][100]) {
	int i , j;
	// 第一排,背包容量为0,所以c[0][j]赋值为0,
	for(j=0 ; j<=sum ; j++) {
		c[0][j] = 0;
		printf("%3d" , c[0][j]);
	}
	printf("\n");
	// 背包容量从1~sum,物品一个一个判断是否放入
	for(i=1 ; i<=n ; i++) {	
		for(j=0 ; j<=sum ; j++) {
			if(j >= w[i]) {
				c[i][j] = MaxValue(c[i-1][j] , c[i-1][j-w[i]]+v[i]);
			} else {  
				c[i][j] = c[i-1][j];
			}
			printf("%3d" , c[i][j]);
		}
		printf("\n");
	}
}
// 倒退求解哪几个物品放入
void SearchTrace(int sum , int n , int w[] , int x[] , int c[][100]) {
	int i = n;
	while(i) {
		// 第i个物品没有放入
		if(c[i][sum] == c[i-1][sum]) {
			x[i] = 0;	
		} else { // 第i个物品放入
			x[i] = 1;
			sum -= w[i]; 
		}
		i--;
	}
}

int main() {
	int n , i , sum;
	int c[100][100];
	int w[100] , v[100]; // 重量,价值
	int x[100]; // 记录第i个是否放入
	printf("输入背包的容量:\n");
	scanf("%d" , &sum);
	printf("输入物品的个数:\n");
	scanf("%d" , &n);
	printf("分别输入每个物品的(重量 价值)\n");
	for(i=1 ; i<=n ; i++) {
		scanf("%d %d" , &w[i] , &v[i]);
	}
	printf("计算过程如下:\n");
	
	Package(sum , n , w , v , c);
	
	SearchTrace(sum , n , w , x , c);
	printf("选择物品如下:\n");
	for(i=1;i<=n;i++) {
		if(x[i] == 1) {
			printf("第%d个物品 : (%d %d) \n" , i , w[i] , v[i]);
		}
	}
	printf("总价值为 : %d\n" , c[n][sum]);
	return 0;
}


转自:http://www.cnblogs.com/fly1988happy/archive/2011/12/13/2285377.html











评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值