0、1背包问题

题目:一个背包承受能力为m,有n个物品,重量和价值不等。选择一些物品放在背包里面,使背包里面东西总价值最大。

算法基本思想:

    利用动态规划思想 ,子问题为:f[i][w]表示前i件物品恰放入一个容量为w的背包可以获得的最大价值。

    其状态转移方程是:f[i][w]=max{f[i-1][w],f[i-1][w-c[i]]+v[i]}    //这个方程非常重要,基本上所有跟背包相关的问题的方程都是由它衍生出来的。

     解释一下上面的方程:“将前i件物品放入容量为w的背包中”这个子问题,如果只考虑第i件物品放或者不放,那么就可以转化为只涉及前i-1件物品的问题,即1、如果不放第i件物品,则问题转化为“前i-1件物品放入容量为v的背包中”;2、如果放第i件物品,则问题转化为“前i-1件物品放入剩下的容量为w-c[i]的背包中”(此时能获得的最大价值就是f [i-1][w-c[i]]再加上通过放入第i件物品获得的价值v[i])。则f[i][w]的值就是1、2中最大的那个值。

   (注意:f[i][w]有意义当且仅当存在一个前i件物品的子集,其费用总和为v。所以按照这个方程递推完毕后,最终的答案并不一定是f[N] [V],而是f[N][0..V]的最大值。)

     写得不错:点击打开链接


动态规划实现

      根据上面的状态转移方程,我们很容易写出如下代码:

#include <iostream>

using namespace std;

const int MAX = 5;
int weight[MAX+1] = {0,7,6,2,1,7};//第0个不算
int value[MAX+1] = {0,15,6,7,12,8};

//注意,要使背包恰好装满,只有dp[0][0]=0,其他为无穷大
//在第二种方法的时候,即dp[0]=0,其他为无穷大
bool mark[100][100] = {0};
int dp[100][100] = {0};

int packWeight = 10;


void OneZeroPack()
{
	int i,j;//i表示第i个物品,j表示目前背包装的东西总量
	for(i=1; i<=MAX; i++){
		for(j=1; j<=packWeight; j++){
			dp[i][j] = dp[i-1][j];
			if(j>=weight[i]&&dp[i-1][j-weight[i]]+value[i]>dp[i-1][j]){
				dp[i][j] = dp[i-1][j-weight[i]]+value[i];
				mark[i][j] = true;
			}
		}
	}


	cout<<"装在背包里面物品最大价值:"<<dp[MAX][packWeight]<<endl;
	cout<<"包括如下:";

	i = MAX;
	j = packWeight;
	while(i>0){
		if(mark[i][j] == true){
			cout<<weight[i]<<" ";
			j -= weight[i];
		}
		--i;
	}
	cout<<endl;
}

int main()
{
	OneZeroPack();
	system("pause");
	return 0;
}
 

递归与动态规划直接的转换

      根据种方式我们可以递归来编写程序,当然了这中方案是最不可取的。但是,我只是说明一下,这种自底向上的问题都可以转换为递归问题,相反我们在优化递归问题的时候就首先应该想到动态规划

/*Rights reserved lsj @ jy*/
#include <iostream>

using namespace std;

const int packWeight = 10;//背包承受的重量
const int size= 5; //物品数量

int weight[size+1] = {0,7,6,2,1,7};//第0个不算  
int value[size+1] = {0,15,6,7,12,8};  

const int maxNum = 100;
int dp[2][maxNum] = {0};

//thingNum表示物品的个数
//packWeight表示背包能够承受的重量
//f[i][w] = max(f[i-1][w],f[i-1][w-weight[i]]+value[i])
int ZeroOnePackRecursively(int thingNum,int packWeith)
{
    if(thingNum == 0) return 0; 
	
	int packValue = ZeroOnePackRecursively(thingNum-1,packWeith);
	if(weight[thingNum]<=packWeith){
		int subPackValue = ZeroOnePackRecursively(thingNum-1,packWeith-weight[thingNum])
			           + value[thingNum];
		return packValue>subPackValue?packValue:subPackValue;
	}
	return packValue;
}

int main()  
{  
	cout<<ZeroOnePackRecursively(size,packWeight)<<endl;
	system("pause");  
	return 0;  
}  

动态规划滚动数组优化

    动态规划的常用优化方式,滚动数组。那么在做0,1背包问题的时候,我们并不需要dp[n][m]这多的空间,我们只需要申请dp[2][100]的数组空间就够了:

/*Rights reserved lsj @ jy*/
#include <iostream>

using namespace std;

const int packWeight = 10;//背包承受的重量
const int size= 5; //物品数量

int weight[size+1] = {0,7,6,2,1,7};//第0个不算  
int value[size+1] = {0,15,6,7,12,8};  

const int maxNum = 100;
int dp[2][maxNum] = {0};

//thingNum表示物品的个数
//packWeight表示背包能够承受的重量
//f[i][w] = max(f[i-1][w],f[i-1][w-weight[i]]+value[i])
int ZeroOnePackRollArray(int thingNum,int packWeight)
{
	int index = 1;//滚动数组的索引,利用index使它滚动起来

	for(int i=1; i<=thingNum; ++i){//考虑第i个物品
		for(int j=0; j<=packWeight; j++){
			dp[index][j] = dp[1-index][j];
			if(j>=weight[i] && (dp[1-index][j-weight[i]]+value[i])>dp[1-index][j])
				dp[index][j] = dp[1-index][j-weight[i]]+value[i];
		}
		index = 1 - index; //滚动起来
	}
	return dp[index][packWeight];//注意一定是[index][packWeight]
}

int main()  
{  
	cout<<ZeroOnePackRollArray(size,packWeight)<<endl;
	system("pause");  
	return 0;  
}

关于0、1背包问题的继续优化

   如果只使用一维数组f[0…v],我们要达到的效果是:第i次循环结束后f[v]中所表示的就是使用二维数组时的f[i][v],即前i个物体面对容量v时的最大价值。我们知道f[v]是由两个状态得来的,f[i-1][v]和f[i-1][v-c[i]],使用一维数组时,当第i次循环之前时,f[v]实际上就是f[i-1][v],那么怎么得到第二个子问题的值呢?事实上,如果在每次循环中我们以v=v…0的顺序推f[v]时,就能保证f[v-c[i]]存储的是f[i-1][v-c[i]]的状态。

#include <iostream>

using namespace std;

const int packWeight = 10;
const int MAX = 5;
int weight[MAX+1] = {0,7,6,2,1,7};//第0个不算
int value[MAX+1] = {0,15,6,7,12,8};


//注意,要使背包恰好装满,只有dp[0][0]=0,其他为无穷大
//在第二种方法的时候,即dp[0]=0,其他为无穷大
int dp1[100] = {0};

void OneZeroPackOptimize()//这种做法,也是可以输出的背包的东西
{
	for(int i=1; i<=MAX; ++i){
		//一定要反过来输入,不然值会覆盖
		for(int j=packWeight; j>=weight[i]; --j){
			if(dp1[j]<dp1[j-weight[i]]+value[i])
				dp1[j] = dp1[j-weight[i]]+value[i];
		}
	}
	cout<<"装在背包里面物品最大价值:"<<dp1[packWeight]<<endl;
}

int main()
{
	OneZeroPackOptimize();
	system("pause");
	return 0;
}


0-1背包恰好背满

     在01背包中,有时问到“恰好装满背包”时的最大价值,与不要求装满背包的区别就是在初始化的时候,其实对于没有要求必须装满背包的情况下,初始化最大价值都为0,是不存在非法状态的,所有的都是合法状态,因为可以什么都不装,这个解就是0,但是如果要求恰好装满,则必须区别初始化,除了f[0]=0,其他的f[1…v]均设为-∞或者一个比较大的负数来表示该状态是非法的。

     这样的初始化能够保证,如果子问题的状态是合法的(恰好装满),那么才能得到合法的状态;如果子问题状态是非法的,则当前问题的状态依然非法,即不存在恰好装满的情况。

【注】在初始化的时候注意,是要完全装满,还是可以不装满。


0-1背包输出最优方案

    一般来讲,背包问题都是求一个最优值,但是如果要求输出得到这个最优值的方案,就可以根据状态转移方程往后推,由这一状态找到上一状态,依次向前推即可。

    这样就可以有两种实现方式,一种是直接根据状态转移矩阵向前推(代码看下面)另一种就是使用额外一个状态矩阵记录最优方案的路径,道理都是一样的。(请看第一个动态规划的代码的mark数组)

i = N-1;
j = V;
while(i >= 0)
{
     if(maxValue[i][j] == maxValue[i-1][j-weight[i]] + value[i])
     {
         printf("%d ",i);
         j = j - weight[i];
     }
     --i;
}

参考:http://www.ahathinking.com/archives/95.html,上面很多语言描述都是引自这位前辈。


后记

     0、1背包经典例题,此题必看:http://blog.csdn.net/lsjseu/article/details/11660731

     本博客有些关于完全背包的例子:

   (1)捞鱼问题:http://blog.csdn.net/lsjseu/article/details/11684873

   (2)跳台阶问题:http://blog.csdn.net/lsjseu/article/details/11583513

   (3)整数和分解问题:http://blog.csdn.net/lsjseu/article/details/9390443

   (3)素数和分解问题:http://blog.csdn.net/lsjseu/article/details/11850585

 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值