浅析动态规划之背包问题

动态规划—背包问题

动态规划方法代表了这一类问题(最优解结构or子问题最优性)的一般解法,是设计方法或者策略,不是具体算法。
本质是递推,核心是找到状态转移的方式,写出dp方程
形式:
—记忆性递归
—递推

动态规划(DP)在程序设计竞赛中经常被选作题材,有许多经典的DP问题,背包问题就是其中最典型的一种

01背包问题

有n个重量和价值分别为wi,vi的物品。从这些物品中跳出总重量不超过W的物品,求所有挑选方案中价值总和的最大值。
!限制条件
1<=n<=100
1<=wi,vi<=100
1<=W<=10000
样例
输入

n=4
(w,v)={(2.3),(1,2),(3,4),(2,2)}
W=5

输出

7(选择013号物品)

题目分析

首先这道题目是可以用深搜来解决的,但是深搜的话每一层的搜索都需要两次分支,最坏就需要O(2^n)的时间,当n比较大的时候就没办法解决了。并且最重要的是在深搜中会出现数据重复计算的情况,极大浪费了计算时间。所以我们会想到是否可以把第一次计算的结果记录下来,在第二次遇到同样的数据时直接调用。
这就是记忆化搜索的思想,动态规划也是利用了这一点。
构造DP表
例如dp[1][3],此时剩余背包容量为3,如果要这个(1,2)这个物品,那么剩余容量变为2,此时看dp[0][2]=3,所以价值v=3+2;如果不要这个物品,此时dp[1][3]=dp[0][3],两者取其max,得到5。
依律可构造出完整dp表,然后依据dp表写出dp式。

代码(C++)

#include<iostream>
#include<algorithm>
using namespace std;
int dp[101][101];		//DP数组
void solve(int n,int w[], int v[],int W)
{
	//构建dp表
	for (int i = 0; i <= W; i++)	//初始化第一行
	{
		if (i < w[0])
		{
			dp[0][i] = 0;
		}
		else
		{
			dp[0][i] = v[0];
		}
		cout << dp[0][i] << "  ";
	}
	cout << endl;

	for (int i = 1; i < n; i++)	//其他行
	{
		for (int j = 0; j <= W; j++)
		{
			if (j < w[i])
			{
				dp[i][j] = dp[i - 1][j];
				cout << dp[i][j] << "  ";
			}
			else
			{
				dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - w[i]] + v[i]);
				cout << dp[i][j] << "  ";
			}
		}
		cout << endl;
	}
	cout << endl << "物品价值总和的最大值为:" << dp[n - 1][W];
}
int main()
{
	int n,W;
	cout << "请输入物品个数:" << endl;
	cin >> n;
	int *w = (int *)malloc(sizeof(int)*n);
	int *v = (int *)malloc(sizeof(int)*n);
	cout << "请依次输入物品的重量和价值:" << endl;
	for (int i = 0; i < n; i++)
	{
		cin >> w[i] >> v[i];
	}
	cout << "请输入物品总和限制的重量:" << endl;
	cin >> W;
	cout << "dp表如下:" << endl;
	solve(n, w, v, W);
}

!!注意
1.为了更加明了,代码的输入输出与样例略有差异。
2.构造dp表最重要的的一点是对DP数组进行初始化,初始化DP数组第一行时一定要注意。
3.dp[i-1][j-w[i]]+v[i]这里的代码表示的是要这个物品的价值+背包容量减去该物品重量后的最大价值(这个最大价值就是依据前面递归推导的)

完全背包问题

有n种重量和价值分别为wi,vi的物品。从这些物品中跳出总重量不超过W的物品,求所有挑选方案中价值总和的最大值。在这里,每种物品可以挑选任意多件。
!限制条件
1<=n<=100
1<=wi,vi<=100
1<=W<=10000
样例
输入

n=3
(w,v)={(3.4),(4,5),(2,3)}
W=7

输出

100号物品选择1个,2号物品选择2个)

题目分析

物品可以选多件,那么在01背包问题的基础上就需要多一层循环

for(int k=0;k*w[i]<=j;k++)
	dp[i+1][j]=max(dp[i+1][j],dp[i][j-k*w[i]]+k+v[i])

但是多加的这层循环就导致算法的复杂度为O(nW^2),我们可以发现这个三重循环下是存在多余的计算的,在dp[i+1][j]的计算选择k(k>=1)个的情况,与在dp[i+1][j-w[i]]的计算中选择k-1个的情况是相同的,所以dp[i+1][j]的递推中k>=1部分的计算已经在dp[i+1][j-w[i]]的计算中完成了。
所以我们可以不需要k的循环了。

代码(C++)

#include<iostream>
#include<algorithm>
using namespace std;
int dp[101][101];		//DP数组
void solve(int n, int w[], int v[], int W)
{
	//构建dp表
	for (int i = 0; i <= W; i++)		//初始化第一行
	{
		if (i >= w[0])
		{
			dp[0][i] = (i / w[0])*v[0];
		}
		else
		{
			dp[0][i] = 0;
		}
		cout << dp[0][i] << "  ";
	}
	cout << endl;

	for (int i = 1; i < n; i++)			//其他行
	{
		for (int j = 0; j <= W; j++)
		{
			if (j < w[i])
			{
				dp[i][j] = dp[i - 1][j];
			}
			else
			{
				dp[i][j] = max(dp[i - 1][j], dp[i][j - w[i]] + v[i]);
			}
			cout << dp[i][j] << "  ";
		}
		cout << endl;
	}
	cout << endl << "物品价值总和的最大值为:"<< dp[n-1][W];
}
int main()
{
	int n, W;
	cout << "请输入物品个数:" << endl;
	cin >> n;
	int *w = (int *)malloc(sizeof(int)*n);
	int *v = (int *)malloc(sizeof(int)*n);
	cout << "请依次输入物品的重量和价值:" << endl;
	for (int i = 0; i < n; i++)
	{
		cin >> w[i] >> v[i];
	}
	cout << "请输入物品总和限制的重量:" << endl;
	cin >> W;
	cout << "dp表如下:" << endl;
	solve(n, w, v, W);
}

!!注意
1.在dp表构造其他行时,要注意对j<w[i]的判断,否则会导致max(dp[i - 1][j], dp[i][j - w[i]] + v[i])出错。
2.上述两个问题的代码都输出了dp表,可以对照思考。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值