01背包问题解题思路

本文围绕01背包问题展开,介绍了使用动态规划解决该问题的方法。通过寻找递推关系式,利用滚动数组优化空间。还阐述了根据最优解回溯找出解的组成的方式,对比了动态规划与蛮力法的优劣,指出动态规划可将复杂问题拆分为小问题并记录解,减少计算量。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

问题描述:有n 个物品,它们有各自的重量和价值,现有给定容量的背包,如何让背包里装入的物品具有最大的价值总和?

F(i,j)表示取前i个物品,使他们总体积不超过j的最优取法取得的价值总和

寻找递推关系式,面对当前商品有两种可能性:
第一,包的容量比该商品体积小,装不下,此时的价值仍为上一个状态(i-1)的价值且当前容量不变,即F(i,j)=F(i-1,j);
第二,还有足够的容量可以装该商品,但装了也不一定达到当前最优价值,装了之后即F(i,j)=F(i-1 , j - w(i))+v(i);这里的j为上个状态留下来的容量,装了之后要使上个状态的j - w(i)才为装了当前物品的容量,且价值总和加v(i))
所以在装与不装之间选择最优的一个,即F(i,j)=max{ F(i-1 , j),F(i-1 , j - w(i))+v(i) }

解决背包问题若使用记忆型递归,则需要二维数组F[i][j],容量很大时会超出内存
注意到这个二维数组的下一行的值,只用到了上一行(i-1,j)及左边(i-1, j - w(i)的值,现使用滚动数组的思想
**滚动数组:**上一行的值使用完之后便不会再使用了,照成空占内存的浪费,现让新一行的地址存在上一行的地址,就不用创建这么多的内存空间

我们现在求红圈F(i,j)的值,需要用到F(i-1,j-(w))(位于F(i,j)的左上边)和F(i-1,j) (位于F(i,j)的上边)在的值,F(i,j)可以从左往右算,也可以从右往左算,现让F(i,j)可以从右往左算,算出来之后边代替上一行的值(此时F(i-1,j)已经没有用了),便可以将算出的值放在上一行的位置中。

最优解即是V(number,capacity)=V(4,8)=10,但还不知道解由哪些商品组成,故要根据最优解回溯找出解的组成,根据填表的原理可以有如下的寻解方式:
    1) V(i,j)=V(i-1,j)时,说明没有选择第i 个商品,则回到V(i-1,j);
    2) V(i,j)=V(i-1,j-w(i))+v(i)实时,说明装了第i个商品,该商品是最优解组成的一部分,随后我们得回到装该商品之前,即回到V(i-1,j-w(i));
    3) 一直遍历到i=0结束为止,所有解的组成都会找到。
  j) 如上例子,
    1) 最优解为V(4,8)=10,而V(4,8)!=V(3,8)却有V(4,8)=V(3,8-w(4))+v(4)=V(3,3)+6=4+6=10,所以第4件商品被选中,并且回到V(3,8-w(4))=V(3,3);
    2) 有V(3,3)=V(2,3)=4,所以第3件商品没被选择,回到V(2,3);
    3) 而V(2,3)!=V(1,3)却有V(2,3)=V(1,3-w(2))+v(2)=V(1,0)+4=0+4=4,所以第2件商品被选中,并且回到V(1,3-w(2))=V(1,0);
    4) 有V(1,0)=V(0,0)=0,所以第1件商品没被选择;

 #include<iostream>
#include<algorithm>
using namespace std;
#define N 4
#define cap 8
int f[N + 1][cap + 1], w[N + 1] = { 0, 2, 3, 4, 5 }, v[N + 1]{0, 3, 4, 5, 6};
int sect[N];
void finwhat(int i,int j){
	if (i > 0){
		if (f[i][j] == f[i - 1][j]){
			sect[i] = 0;
			finwhat(i - 1, j);
		}
		else if (j - w[i]>=0 && f[i][j] == f[i - 1][j - w[i]] + v[i])
		{
			sect[i] = 1;
			finwhat(i - 1, j - w[i]);
		}
	}
}
/*二维数组存储部分*/
//int main(void){	
//	int i,j;
//	for ( i = 1; i <= N; i++){
//		for ( j = 1; j <= cap; j++){
//			if (j < w[i])
//				f[i][j] = f[i - 1][j];
//			else{
//				if (f[i - 1][j]>f[i - 1][j - w[i]] + v[i])
//					f[i][j] = f[i - 1][j];
//				else
//					f[i][j] = f[i - 1][j - w[i]] + v[i];
//			}
//			cout << f[i][j]<<" ";
//		}
//		cout << endl;
//	}
//	
//	finwhat(i-1, j-1);
//	for (int i = 0; i <= N; i++)
//		cout << sect[i]<<" ";
//		
//	
//	return 0;
//}
/*空间优化部分*/
int f1[cap + 1];
int main(){
	int i, j;
	for (i = 1; i <= N; i++){
		for (j = cap; j >= 0; j--){
			if (j < w[i])
				f1[j] = f1[j];
			else{
				f1[j] = max(f1[j], f1[j - w[i]]+v[i]);
			}
			cout << f1[j] << " ";
		}
		cout << endl;
	}
	cout <<"ans:"<< f1[cap];
	return 0;
}

总结:
  对于01背包问题,用蛮力法与用动态规划解决得到的最优解和解组成是一致的,所以动态规划解决此类问题是可行的。动态规划效率为线性,蛮力法效率为指数型,结合以上内容和理论知识可以得出,解决此问题用动态规划比用蛮力法适合得多。对于动态规划不足的是空间开销大,数据的存储得用到二维数组;好的是,当前问题的解只与上一层的子问题的解相关,所以,可以把动态规划的空间进行优化,使得空间效率从O(n*c)转化为O©,遗憾的是,虽然优化了空间,但优化后只能求出最优解,解组成的探索方式在该方法运行的时候已经被破坏掉;总之动态规划和优化后的动态规划各有优缺点,可以根据实际问题的需求选择不同的方式。
动态规划可以解决哪些类型的问题?
待解决的原问题较难,但此问题可以被不断拆分成一个个小问题,而小问题的解是非常容易获得的;如果单单只是利用递归的方法来解决原问题,那么采用的是分治法的思想,动态规划具有记忆性,将子问题的解都记录下来,以免在递归的过程中重复计算,从而减少了计算量。
参照博客:https://www.cnblogs.com/Christal-R/p/Dynamic_programming.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值