【动态规划】从0开始解决并优化01背包问题

本文详细介绍了01背包问题的解决方案,包括如何使用动态规划解决该问题,状态的表示和计算方式,递归与迭代版本的代码实现,以及状态表示的优化,通过一维数组减少空间复杂度。
摘要由CSDN通过智能技术生成

一、01背包问题:

有一个最大容量为\(v\)的背包,有一系列物品,每一个物品都有体积\(v_i\)和价值\(w_i\)两个属性,要求选出一部分物品(每个物品只能选一次),在背包能装得下它们的前提下使其价值之和最大。

要解决这个问题,需要使用动态规划的方法。

二、动态规划的最基本内容:

动态规划的最基本内容包含状态的表示和计算:

1.状态的表示:

状态的表示有两个组成部分:状态表示的集合和状态表示的性质。例如,在01背包问题中,我们可以使用\(f\left ( i,j \right )\)来表示从前i个物品中选出体积不超过j的所有组合的价值最大值,其中:“从前i个物品中选出体积不超过j的所有组合”就是状态表示的集合;“价值最大值”就是状态表示的性质。

2.状态的计算:

同样是01背包问题(假设问题给出的条件是i个物品,限制体积是j),为了计算出最终所需的结果\(f\left ( i,j \right )\),我们需要对集合进行划分(这样的划分一般要满足不重不缺的条件:同一种情况不能出现在不同的划分中;任何一种情况必须要有所属的划分)。

在此问题中,可以将\(f\left ( i,j \right )\)划分为:①不包含第i个物品的所有情况;②包含第i个物品的所有情况。经过分析,①可表示为\(f\left ( i-1,j \right )\),②可表示为\(f\left ( i-1,j-v_i \right )+w_i\),最终\(f\left ( i,j \right )\)的值就为max(①,②)。以此类推,从而可以由\(f\left ( 1,j_1 \right )\)逐步计算出结果。

三、代码:

要注意动态规划与递归的不同。

本问题也可以使用递归进行计算:编写一个函数\(f\left ( i,j \right )\),返回值是\(f\left ( i-1,j \right )\)和\(f\left ( i-1,j-v_i \right )+w_i\)中的较大者,只需要规定好\(f\left ( 1,j_1 \right )\)的一系列值,就可以在主函数中直接输出函数\(f\left ( i,j \right )\)的返回值。

代码如下(递归):

/*
输入:物品的数量 背包的容量
接下来分别输入每个物品的体积和价值

输出:最大价值
*/
#include <iostream>
using namespace std;
typedef pair<int,int> pii;
const int N = 1010;

pii thing[N];//从1开始,体积,价值
int n, v;

int f(int i, int j) {
	if (i > 1) {
		int x = max(f(i - 1, j), f(i - 1, j - thing[i].first) + thing[i].second);
		return x;
	}
	else {
		if (j < 0)return -99999;
		else if (j < thing[1].first)return 0;
		else return thing[1].second;
	}
}
void solution() {
	cin >> n >> v;
	for (int i = 1; i <= n; i++) {
		cin >> thing[i].first >> thing[i].second;
	}
	cout << f(n, v);
}
int main() {
	int t = 1;
	//cin >> t;
	while (t--) {
		solution();
	}
}

 在上文代码中,由于每一次函数调用都会进一步调用两次函数,所以当数据量较大的时候,就很容易导致运行超时。因此,可以从简单情况开始,逐步递推,最终得出所需结果。

代码如下(递推):

/*
输入:物品的数量 背包的容量
接下来分别输入每个物品的体积和价值

输出:最大价值
*/
#include <iostream>
using namespace std;
const int N = 1010;

pii thing[N];//从1开始,体积,价值
int n, v;
int f[N][N];

void solution() {
	cin >> n >> v;
	for (int i = 1; i <= n; i++) {
		cin >> thing[i].first >> thing[i].second;
	}
	for (int i = 1; i <= n; i++) {
		for (int j = 0; j <= v; j++) {
			if (j - thing[i].first >= 0)f[i][j] = max(f[i - 1][j], f[i - 1][j - thing[i].first] + thing[i].second);
			else f[i][j] = f[i - 1][j];
		}
	}
	cout << f[n][v];
}
int main() {
	int t = 1;
	//cin >> t;
	while (t--) {
		solution();
	}
}

四、优化:

01背包问题中的状态表示是可以优化的:我们可以使用\(f(j)\)来表示体积不超过j的所有组合的价值最大值。

为什么可以这样优化呢?是因为每一次求\(f\left ( i,j \right )\)的值时,只需要访问\(f\left ( i-1,j \right )\)和\(f\left ( i-1,j-v_i \right )+w_i\),发现他们都是\(f(i-1, )\)层的数据,更早的数据已经失去意义,因此,我们可以舍弃\(f(i)\)这个维度,直接使用一维数组进行计算,省下大量空间。

代码如下:

/*
输入:物品的数量 背包的容量
接下来分别输入每个物品的体积和价值

输出:最大价值
*/
#include <iostream>
using namespace std;
const int N = 100010;

class Thing {
public:
	int v, w;
};

Thing thing[N];//从1开始,体积,价值
int n, v;
int f[N];

void solution() {
	cin >> n >> v;
	int num = 0;
	for (int i = 1; i <= n; i++) {
		cin >> thing[i].v >> thing[i].w;
	}
	for (int i = 1; i <= n; i++) {
		for (int j = v; j >= thing[i].v; j--) {
        //为什么j要从v开始:为了不使新数据覆盖可能用到的旧数据
        //j的结束点不是0:要访问f[j - thing[i].v],如果j<thing[i].v就无意义(也可加上一个判断)
			f[j]=max(f[j], f[j - thing[i].v] + thing[i].w);
		}
	}
	cout << f[v];
}
int main() { 
	int t = 1;
	//cin >> t;
	while (t--) {
		solution();
	}
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值