0-1背包问题——从暴力到一维vector的优化

0-1背包问题

分析:同样,若没有思路可以考虑暴力循环的方法,即先找到多少种情况(2n),再每一种情况是否符合要求并求价值,求取最大值,O((2n)*n)。

若进一步考虑,可定义表达式:F(n,c),意思为将n个物品装入容量为c的背包中,能取得的最大值。如下图所示,采用递归的方式可以避免暴力。对于第n个物品,可以有两种取法,取(1)或不取(0),当取的时候,则最大值为v(n)+F(n-1,c-wn),即将n-1个物品装入c-wn的容量中,所能得到的最大价值;当不取的时候,则为F(n-1,c),因为没取n,故容量不变。那么,若采取递归计算的话,实际上每层求max(v(n)+F(n-1,c-wn),F(n-1,c)),并返回给最上层即可。

#include "pch.h"
#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

// 递归法解决0-1背包问题

class Solution
{
public:

	// index为第几个物品,c为当前容量,
	int getMaxValue( vector<int> &weight,  vector<int> &value, int index, int c)
	{
		if (index < 0 || c <= 0) return 0;
		
		int res = 0;

		// 两种情况,取index和不取index,并求最大值
		if (weight[index] <= c) res = value[index] + getMaxValue(weight, value, index-1, c - weight[index]);
		res = max(res, getMaxValue(weight, value, --index, c));

		return res;
	}
};

int main()
{
	vector<int> weight = { 2,1,3,2 };
	vector<int> value = { 12,10,20,15 };
	Solution s;
	cout<<(s.getMaxValue(weight, value, weight.size() - 1, 5));  // 37
	return 0;
}

可采用记忆化搜索对气优化

#include "pch.h"
#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

// 递归法解决0-1背包问题

class Solution
{
public:

	// index为第几个物品,c为当前容量,
	int getMaxValue( vector<int> &weight,  vector<int> &value, int index, int c, vector<vector<int> > &memo)
	{
		if (index < 0 || c <= 0) return 0;

		// 1、
		if (memo[index][c]) return memo[index][c];
		int res = 0;

		// 两种情况,取index和不取index,并求最大值
		if (weight[index] <= c) res = value[index] + getMaxValue(weight, value, index-1, c - weight[index],memo);
		res = max(res, getMaxValue(weight, value, index-1, c, memo));

		// 2、
		memo[index][c] = res;
		return res;
	}
};

int main()
{
	vector<int> weight = { 2,1,3,2 };
	vector<int> value = { 12,10,20,15 };
	// 3、
	vector<vector<int> > memo(weight.size(), vector<int>(6, 0));
	Solution s;
	cout<<(s.getMaxValue(weight, value, weight.size() - 1, 5, memo));
	return 0;
}

进一步,由于该递归中存在最优子结构,故可从下到上采用动态规划的办法。可参考机器走路问题82。不过本题中二维条件不够明显,需进一步分析。首先,题中要求,n个物品最多可装入容量为c的背包的最大价值,写成表达式为F(n,c),那么这是一个二维函数,将82中的竖坐标当作n,横坐标当作容量c,那么F(i,ci)代表前i个物品装入ci背包中能得到的最大价值。。接下来需要找状态转移方程,由于背包可装可不装,那就分情况讨论,若i装,则F(i,ci) = vi+f(i-1,ci-wi):即第i个物品的价值,加上,前i-1个物品装入ci-wi背包中的最大价值。若i不装入,则F(i,ci) = F(i-1,ci):即前i-1个物品装入ci背包中的最大价值。故状态转移方程为F(i,ci) = max(vi+f(i-1,ci-wi),F(i-1,ci))。

#include "pch.h"
#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

// 动态规划解决0-1背包
int main()
{
	vector<int> weight = { 2,1,3,2 };   // 物品重量
	vector<int> value = { 12,10,20,15 }; // 物品价值
		
	// 当成二维图形,横坐标为容量,纵坐标为物品
	int c = 5;
	vector<vector<int> > dp(weight.size(), vector<int>(c+1, 0));
	
	// 第一列不用赋初值,因为第一列的容量都为0,故不可能放入物品
	// 先将第一行赋初值
	for (int i = 0; i <= 5; i++)  
	{
		dp[0][i] = weight[0] <= i ? value[0] : 0;
	}
	for (int i = 1; i < weight.size(); i++)
	{
		for (int j = 1; j <= 5; j++)
		{
			if (weight[i] <= j) dp[i][j] = value[i] + dp[i - 1][j - weight[i]];
			dp[i][j] = max(dp[i-1][j], dp[i][j]);
		}
	}
	cout << dp[weight.size()-1][5];
	return 0;
}

动态规划的优化基本上均是尽可能降低空间复杂度。通常是二维变一维,一维变变量。

1、从上述循环中可以看到,每次循环只用到了两行数据,故可用求余进行处理。

2、再进一步分析,可从右向左进行刷新,这样只保存一行数据就可不断求得值

// 1、变为两行数组
// 动态规划解决0-1背包
int main()
{
	vector<int> weight = { 2,1,3,2 };   // 物品重量
	vector<int> value = { 12,10,20,15 }; // 物品价值
		
	// 当成二维图形,横坐标为容量,纵坐标为物品
	int c = 5;
	// 优化1、
	vector<vector<int> > dp(2, vector<int>(c+1, 0));
	
	// 第一列不用赋初值,因为第一列的容量都为0,故不可能放入物品
	// 先将第一行赋初值
	for (int i = 0; i <= 5; i++)  
	{
		dp[0][i] = weight[0] <= i ? value[0] : 0;
	}
	for (int i = 1; i < weight.size(); i++)
	{
		for (int j = 1; j <= 5; j++)
		{
			// 优化2、
			if (weight[i] <= j) dp[i % 2][j] = value[i] + dp[(i - 1) % 2][j - weight[i]];
			dp[i % 2][j] = max(dp[(i-1) % 2][j], dp[i % 2][j]);
		}
	}
	// 优化3、
	cout << dp[(weight.size()-1) % 2][5];
	return 0;
}
// 动态规划解决0-1背包
int main()
{
	vector<int> weight = { 2,1,3,2 };   // 物品重量
	vector<int> value = { 12,10,20,15 }; // 物品价值
		
	// 当成二维图形,横坐标为容量,纵坐标为物品
	int c = 5;
	
	// 优化1、
	vector<int> dp(c+1, 0);
	
	for (int i = 0; i <= 5; i++)  
	{
		dp[i] = weight[0] <= i ? value[0] : 0;
	}

	for (int i = 1; i < weight.size(); i++)
	{
		// 逆序考虑,则不会覆盖数据
		for (int j = c; j >= 0; j--)
		{
			int tmp = dp[j];
			// 优化2、
			if (weight[i] <= j) dp[j] = value[i] + dp[j - weight[i]];
			dp[j] = max(dp[j], tmp);
		}
	}
	// 优化3、
	cout << dp[c];
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值