动态规划——01背包问题

问题描述:
  给定 n 件物品,第 i 件的物品重量是 w[i] ,价值是 v[i],求在背包承重为 m 时能存放物品的最大总价值。(每个物品仅有一件)。
问题分析:

  • 贪心
      对于该问题,我们的第一想法可能会是贪心,先拿价值大的物品。但是这里有重量的影响,贪心是有后效的(即前面的贪心操作会影响后面的操作),具体什么意思呢?比如给定3件物品,分别具有重量和价值两个属性,【5,4】,【2,3】,【3,2】。将这三件物品放入承重为5的背包,如果运用贪心,先选取价值大的放,放入第一件物品【5,4】后背包就放不下了,此时最大价值为4。但实际上背包放物品【2,3】,【3,2】价值最大为5。由此可见贪心是行不通的。
  • DFS(深度优先搜索)
      dfs搜索所有情况取最大价值即可,但当物品数量较多时dfs的时间复杂度不是我们能接受的。
  • 动态规划(DP)
      本题较好的解法应该是运用动态规划。下面具体分析一下动态规划解01背包。

动态规划解01背包:
核心是对于每种物品只有两个选择——放与不放。
  用 dp[i][j] 表示前 i 件物品放入容量为 j 的空间时的最大价值。将第 i 件物品放入容量为 j 的空间,只有放与不放两种选择:
(1) 放不下(不放),则当前最大价值为 i-1 件物品放入容量为 j 的空间时的最大价值。即:dp[i][j]=dp[i-1][j];
(2) 放的下有两种情况,取最大价值
 ①不放,当前最大价值为 i-1件物品放入容量为 j 的空间时的最大价值,即 dp[i][j]=dp[i-1][j];
 ②放,肯定就要腾出w[i]的空间(因为当前枚举的空间容量固定为 j ),则腾出后的空间为 j-w[i] ,所以此时最大价值为: i-1 件物品放入 j-w[i] 的空间的最大价值加上当前物品的价值。即dp[i][j]=dp[i-1][j-w[i]]+v[i]。
所以放得下的综合式子为:dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+v[i])
注: 这里提到 i-1 件物品放入背包的最大价值,并不意味着 i-1 件物品都被放进去了,只是说已经对 i-1 件物品做出了最大价值的选择,实际可能放了可能没放。当我们将 n 件物品放入[0,m]的空间全部做出最优选择,答案也就出来了——dp[n][m]。
状态转移方程:
在这里插入图片描述
代码:

#include<stdio.h>
#include<string.h>
const int maxn=100;
int dp[maxn][maxn],w[maxn],v[maxn],vis[maxn];
int Max(int a,int b)
{
	if(a>=b)
		return a;
	else
		return b;
}
int main()
{
	int n,m;
	printf("请输入物品个数及背包最大容量:");
	scanf("%d%d",&n,&m);
	printf("请输入每个物品的重量和价值:\n");
	for(int i=1;i<=n;i++)
		scanf("%d%d",&w[i],&v[i]);
	memset(dp,0,sizeof(dp));//批量化初始化dp数组为0
	for(int i=1;i<=n;i++)
	{
		for(int j=0;j<=m;j++)
		{
			if(j<w[i])//装不下
				dp[i][j]=dp[i-1][j];
			else//装得下,两种情况取较大的
				dp[i][j]=Max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]); 
		}
	}
	printf("背包能承载的最大价值为:%d\n",dp[n][m]);
	//回溯
	int i=n,j=m;
	while(i>0)
	{
		//选择了当前物品(由状态转移方程可知)
		if(dp[i][j]!=dp[i-1][j])
		{
			vis[i]=1;//标记
			j-=w[i];//空间减少
		}
		i--;
	}
	printf("选择的物品为:\n");
	for(int i=1;i<=n;i++)
		if(vis[i])
			printf("%d号\t重量为%d\t价值为%d\n",i,w[i],v[i]);
	return 0;
}

测试样例:
5 10
2 6
2 3
6 5
5 4
4 6
输出:

背包能承载的最大价值为:15
选择的物品为:
1号    重量为2    价值为6
2号    重量为2    价值为3
5号    重量为4    价值为6

样例填充图:
5件物品,背包最大承重为10。每个物品的具有重量和价值:【2,6】【2,3】【6,5】【5,4】【4,6】。
在这里插入图片描述
注: 结合代码及样例推导一遍此填充图更有助于理解。
优化:
  以上方法的时间和空间复杂度均为O(n*m),我们还可以将空间复杂度优化到O(m)。
  用dp[j]表示空间为 j 的所有方案中的最大价值。
状态转移方程:dp[j]=Max(dp[j],dp[j-w[i]]+v[i]); (1<=i<=n,w[i]<=j<=m)

for(int i = 1; i <= n; i++)
    	for(int j = m; j >= w[i]; j--)
        	dp[j]=max(dp[j],dp[j-w[i]]+v[i]);

原理: 核心是基于一维数组做反向迭代,这样的话j<w[i]的部分直接不用复制了,且由于是反向的线性更新也不会发生冲突(即不会同一个物品被放多次)。理由很简单,如果我们第二层循环从前往后遍历,因为后面会用到前面的数据,而前面的数据可能是放过该物品后的,这样就有可能导致放入该物品多次。
例:

有一个物品w=1,v=2,状态转移方程:dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
正向迭代,初始状态:dp[0]=0    dp[1]=0    dp[2]=0    dp[3]=0
dp[0]=0    
dp[1]=max(dp[1],dp[1-1]+2)=2
dp[2]=max(dp[2],dp[2-1]+2)=4
dp[3]=max(dp[3],dp[3-1]+2)=6
可以看出该物品被放了三次,显然是错误的

反向迭代,初始状态dp[0]=0    dp[1]=0    dp[2]=0    dp[3]=0
dp[3]=max(dp[3],dp[3-1]+2)=2
dp[2]=max(dp[2],dp[2-1]+2)=2
dp[1]=max(dp[1],dp[1-1]+2)=2
dp[0]=0

代码:

#include<stdio.h>
#include<string.h>
const int maxn=100;
int dp[maxn],w[maxn],v[maxn];
int Max(int a,int b)
{
	if(a>=b)
		return a;
	else
		return b;
}
int main()
{
	int n,m;
	printf("请输入物品个数及背包最大容量:");
	scanf("%d%d",&n,&m);
	printf("请输入每个物品的重量和价值:\n");
	for(int i=1;i<=n;i++)
		scanf("%d%d",&w[i],&v[i]);
	memset(dp,0,sizeof(dp));//批量化初始化dp数组为0
	for(int i=1;i<=n;i++)
		for(int j=m;j>=w[i];j--)
				dp[j]=Max(dp[j],dp[j-w[i]]+v[i]); 
	printf("背包能承载的最大价值为:%d\n",dp[m]);
	return 0;
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值