动态规划入门三---背包问题(1)

序言

背包问题是最广为人知的动态规划问题之一,拥有很多的变形,尽管在理解之后不难写出程序,但是往往要花费一定的时间真正的掌握它。

多阶段决策问题

1引例

物品无限的背包问题
有n种物品,每种均为无穷多个。第i个物品的体积为Vi,重量为Wi。选一些物品装入一个容量为C的背包,是的背包内的物品在总体积不超过C的情况下重量尽可能地大。
1<= n<= 100, 1<=C<=10000, 1<= Wi <= 1000000

【分析】
似乎很眼熟,因为这个就是之前的DAG的硬币问题扩展版,不过增加了一个属性重量。即是原来的无权图便成了有权图。
问题便成了求以C为起点的(终点任意)的,边权之和最大的路径。
此时最大的变化就是把原来的+1,变成了 + W[ i ] 。

2 0--1背包问题

有n种物品,每种只有一个。第i个物品的体积为Vi,重量为Wi。选一些物品装入一个容量为C的背包,是的背包内的物品在总体积不超过C的情况下重量尽可能地大。
1<= n<= 100, 1<=C<=10000, 1<= Wi <= 1000000 。

【分析】
此时可以发现,原来的只靠“剩余体积”这个状态,无法再做此题,因为不知道每个物品是否已经被用过。
也即是原来的状态转移太过混乱,任何时候均可以使用任何物品,所以,我们要把决策有序化!

【解决思想】

引入阶段的概念,具体参见回溯法的理解。

【算法设计】
用d(i,j) 表示当前在第i层,背包剩余剩余容量为j时候接下来的最大重量和,则就分为两种情况:第一种是把第j个物品放进去,此时背包的重量是d(i+1,j -V[ i ] ) + W[ i ] ,
第二种是不放第j个,此时背包的重量是:d(i+1,j)。
所以就有了下面的式子:
d(i,j) = max { d( i+1,j) ,d(i+1,j -V[ i ] ) + W[ i ] }

注意:这个递推的边界处理。下面的代码里面有提及。

提示:多阶段决策的最优化问题往往可以用动态规划来解决,其中,状态及其转移类似于回溯法的解答树。解答树中的“层数”,即是递归函数里面的“当前填充位置”,描述的是即将完成的决策序号,在DP中被称为阶段。。。

其实:说的白一点,所谓的d(i,j)代表的就是把第i个以及其后的n-i+1个物品装到容量为j的背包中的最大重量。

【算法程序代码】

第一个方法:递推法
关键代码如下:

	printf("请输入%d个物品的体积和重量:",n) ;
		for (i=1; i<=n; i ++)
		{
			scanf("%d%d", &v, &wei) ; // 新的状态定义f(i,j)允许我们边读入边计算,而不必把数值存入数组
			for (j=0; j<=c; j++)
			{
				f[i][j] = (i==1 ? 0 : f[i-1][j] );
				if (j >= v)
					if(f[i][j] < f[i-1][j-v] + wei)
						f[i][j] = (f[i-1][j-v] + wei);
			}
		}


第二个方法,一维数组解决。
先上代码:
    for(i=0;i<=n;i++)
    {
        for(j=c;j>=V[i];j--)
        {
            WEI[j]=max(WEI[j],WEI[j-V[i]]+ W[i]);
        }
    }
    printf("第一种的计算结果是:%d\n\n", WEI[c]);


实例实践

加入背包容积为5,
各个物品的体积为2,4,1
各个物品的重量为9,8,1
测试代码如下:

/***** 背包问题 ********/

/******** written by C_Shit_Hu ************/

动态规划///

/****************************************************************************/
/* 
有n种物品,每种只有一个。第i个物品的体积为Vi,重量为Wi。
选一些物品装入一个容量为C的背包,是的背包内的物品在总体积不超过C的情况下重量尽可能地大。
1<= n<= 100,  1<=C<=10000, 1<= Wi <= 1000000 。
*/
/****************************************************************************/


// 三种算法
#include <stdio.h>
#include <string.h>
#define MAX 100
int V[MAX] , W[MAX],f[MAX][MAX], WEI[MAX] ;
int n, i, j, c, v, wei, ele; 

int max(int x,int y)
{return x>y ? x:y;}

int main()
{
	// 基本信息的输入
	printf("请输入您的背包的容积:");
	scanf("%d", &c);
	printf("请输入您要装入背包的物品种类数目:");
	scanf("%d", &n);
	
	// 第一种第二种需要的数据
	printf("请输入每种物品的体积(以空格隔开):");
	for (i=0; i<n; i++)
		scanf("%d", &V[i]);
	printf("请输入每种物品的重量(以空格隔开):");
	for (i=0; i<n; i++)
		scanf("%d", &W[i]);
	/* 原理介绍
	另外一种对称的状态定义:用f(i,j)表示“把前i个物品装入容量为j的背包中的最大重量和”,由此也可得其状态转移方程:
	f(i,j) == max ( f(i-1), f(i-1,j-V[i]) + W[i] )    // 同样是分为装或不装第i个
	边界是类似的:i=0时为0,j<0时为负无穷,最终答案是f(n, c).
	*/
	// 代码如下:
	for(i=0;i<=n;i++)
    {
        for(j=c;j>=V[i];j--)
        {
            WEI[j]=max(WEI[j],WEI[j-V[i]]+ W[i]);
        }
    }
    printf("第一种的计算结果是:%d\n\n", WEI[c]);
	printf("请输入%d个物品的体积和重量:",n) ;
	for (i=1; i<=n; i ++)
	{
		scanf("%d%d", &v, &wei) ; // 此处就是两种方法的区别:新的状态定义f(i,j)允许我们边读入边计算,而不必把数值存入数组
		for (j=0; j<=c; j++)
		{
			f[i][j] = (i==1 ? 0 : f[i-1][j] );
			if (j >= v)
				if(f[i][j] <= f[i-1][j-v] + wei)
					f[i][j] = (f[i-1][j-v] + wei);
		}
	}
	printf("第二种的计算结果是:%d\n\n", f[n][c]);
	return 0;
}


/******************************************************/
/********************  心得体会  **********************/
/*

*/
/******************************************************/

测试结果如下:



问题求助


下面代码的注释部分,觉得算法没有问题,为何就是结果不对呢?

/***** 背包问题 ********/

/******** written by C_Shit_Hu ************/

动态规划///

/****************************************************************************/
/* 
有n种物品,每种只有一个。第i个物品的体积为Vi,重量为Wi。
选一些物品装入一个容量为C的背包,是的背包内的物品在总体积不超过C的情况下重量尽可能地大。
1<= n<= 100,  1<=C<=10000, 1<= Wi <= 1000000 。
*/
/****************************************************************************/


// 三种算法
#include <stdio.h>
#include <string.h>
#define MAX 100
int V[MAX] , W[MAX], d[MAX][MAX], f[MAX][MAX], h[MAX], WEI[MAX] ;
int n, i, j, c, v, wei, ele; 

int max(int x,int y)
{return x>y ? x:y;}

int main()
{
	// 基本信息的输入
	printf("请输入您的背包的容积:");
	scanf("%d", &c);
	printf("请输入您要装入背包的物品种类数目:");
	scanf("%d", &n);
	
	// 第一种第二种需要的数据
	printf("请输入每种物品的体积(以空格隔开):");
	for (i=0; i<n; i++)
		scanf("%d", &V[i]);
	printf("请输入每种物品的重量(以空格隔开):");
	for (i=0; i<n; i++)
		scanf("%d", &W[i]);
		/*   for (i=0; i<n; i++)
		printf("%d  ", V[i]);
		for (i=0; i<n; i++)
        printf("%d ",W[i]);
		// 第一种解决方案:递推思想解决,答案是d[1][c]
		for (i=n; i>=1; i--)
		for (j=0; j<=c; j++)
		{
		d[i][j] = (i==n ? 0 : d[i+1][j] );
		if (j >= V[i])
		if(d[i][j] < (d[i+1][j-V[i]] + W[i]))
		d[i][j] = ( d[i+1][j-V[i]] + W[i]);
		}
		for (i=n; i>=1; i--)
		for (j=0; j<=c; j++)
		printf("%d  ",d[i][j] );
		printf("\n\n第一种的计算结果是:%d\n\n", d[1][c]);
	*/	
	// 改变方向
	/* 原理介绍
	另外一种对称的状态定义:用f(i,j)表示“把前i个物品装入容量为j的背包中的最大重量和”,由此也可得其状态转移方程:
	f(i,j) == max ( f(i-1), f(i-1,j-V[i]) + W[i] )    // 同样是分为装或不装第i个
	边界是类似的:i=0时为0,j<0时为负无穷,最终答案是f(n, c).
	*/
	// 代码如下:
	for(i=0;i<=n;i++)
    {
        for(j=c;j>=V[i];j--)
        {
            WEI[j]=max(WEI[j],WEI[j-V[i]]+ W[i]);
        }
    }
    printf("第一种的计算结果是:%d\n\n", WEI[c]);
	printf("请输入%d个物品的体积和重量:",n) ;
	for (i=1; i<=n; i ++)
	{
		scanf("%d%d", &v, &wei) ; // 此处就是两种方法的区别:新的状态定义f(i,j)允许我们边读入边计算,而不必把数值存入数组
		for (j=0; j<=c; j++)
		{
			f[i][j] = (i==1 ? 0 : f[i-1][j] );
			if (j >= v)
				if(f[i][j] <= f[i-1][j-v] + wei)
					f[i][j] = (f[i-1][j-v] + wei);
		}
	}
	printf("第二种的计算结果是:%d\n\n", f[n][c]);
	/*
	//滚动数组方式
	// 代码如下:
	memset(h, 0, sizeof(h)) ;
	printf("请输入%d个物品的体积和重量:", n);
	for(i=1; i<=n; i++)
	{
	scanf("%d%d", &v, &wei) ; 
	for(j=c; j>=0; j--)
	if(j>=v)
				if (h[j] >= h[j-v]+wei)
				h[j] = h[j-v] + wei;
				}
				printf("第三种的计算结果是:%d\n", h[c]);
				
				  return 0 ;
	*/
}


/******************************************************/
/********************  心得体会  **********************/
/*

*/
/******************************************************/

下一步。。。经典背包问题九讲的学习。

【未完待续……】








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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值