0-1背包问题及变种

0-1背包问题:

有N件物品和一个容量为V的背包。第i件物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。

这个问题的特点是:每种物品只有一件,可以选择放或者不放。

算法基本思想:

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

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

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

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

以上概念和算法基本思想来源于blog:http://www.cnblogs.com/fly1988happy/archive/2011/12/13/2285377.html

以下为算法代码:是个不断优化的过程。首先(1)给出了递归和迭代的思想,(2)然后对迭代的算法代码进行空间优化。(3)然后给出了该问题的变种:恰好装满背包和完全背包问题。

(1)递归方法:

// 0-1 背包问题递归方法  回溯求最佳解
int packageRecursion(int c, int n)
{
	if(n == C)
		return (c < weight[n])? 0 : value[n];
	if(c < weight[n])
		return packageRecursion(c, n+1);
	return max(packageRecursion(c, n+1), packageRecursion(c - weight[n], n+1) + value[n]);
}

(2)迭代方法:

//  0-1 背包问题迭代方法
void packageIteraction()
{
	int **F;
	F = new int*[N+1];   // 申请内存空间
	int i;
	for(i = 0; i < N+1; i++)
	{
		F[i] = new int [C+1];

	}

	int j;
	for(i = 0 ; i < N+1; i++)
	{
		for(j = 0; j < C+1; j++)
		{
			F[i][j] = 0;              // 初始化
		}
	}

	// 正好装满背包 加上这几行
	/*for(i = 0 ; i < N+1; i++)
	{
		for(j = 1; j < C+1; j++)
		{
			F[i][j] = min;
		}
	}*/

	for(i = 1; i < N+1; i++)
	{
		for(j = weight[i]; j < C+1; j++)
		{
			F[i][j] = F[i-1][j] > (F[i-1][j-weight[i]]+value[i])? F[i-1][j] : (F[i-1][j-weight[i]]+value[i]);
		}
	}
	if(F[N][C] > 0)
	{
		cout << "the opt value:" << F[N][C] << endl;
		j = C;                //  回溯 遍历出所选择的节点
		for(i = N; i >= 1; i--)
		{
			if(F[i][j] == (F[i-1][j-weight[i]]+value[i]))
			{
				cout << i << "weight=: " << weight[i] << " value=: "<< value[i] << endl;
				j = j - weight[i];
			}
		}
	}
	else{
		cout << "not finde the opt value" << endl;
	}

	for(i = 0; i < N+1; i++)     // 释放内存空间
	{
		delete[] F[i];
	}
	delete[] F;

}

(3)迭代方法优化空间复杂度:

      以上迭代方法的时间复杂度和空间复杂度都是O(N*V),其中时间复杂度基本已经不能再优化了,但空间复杂度却可以优化到O(V)。此时的做法是用F[V]来存储当前获得的价值的最大值,第二个for循环反向遍历。

//  将j=V:weight[i] 只用一维数组保存就可以了,降低了空间复杂度
void packageIteractionOpt()
{

	int *F;
	F = new int[C+1];   // 申请内存空间
	int i;
	for(i = 0; i < C+1; i++)
		F[i] = 0;

	// 正好装满背包 加上这句
	/*for(i = 1; i < C+1; i++)
		F[i] = min; */

	for(i = 1; i < N+1; i++)
	{
		for(int j= C; j >= weight[i]; j--)
		{
			F[j] = F[j] > (F[j-weight[i]]+value[i])? F[j] : (F[j-weight[i]]+value[i]);
		}
	}
	cout << F[C] << endl;

	delete []F;     // 释放内存空间
}

Main函数代码:

int main()
{
	cin >> C >> N;
	weight = new int[N+1];
	value = new int[N+1];
	value[0] = 0;
	weight[0] = 0;
	int i;
	for(i = 1; i <= N; i++)
		cin >> value[i] >> weight[i];

	//cout << packageRecursion(C, 1) << endl;   // 递归方法
	//packageIteraction();
	packageIteractionOpt();        // 在同等条件下,降低了空间复杂度

	delete []weight;
	delete []value;
	//cout << int(0x80000000) << endl;     // int 中最小值
	//cout << int(0xfffffffff) << endl; ;            // int中-1

	return 0;
}

输出结果:

(4)变种

       1:恰好装满背包

在初始化时除了f[0]为0其它f[1..V]均设为-∞,这样就可以保证最终得到的f[N]是一种恰好装满背包的最优解。如果不能恰好满足背包容量,即不能得到f[V]的最优值,则此时f[V]=-∞,这样就能表示没有找到恰好满足背包容量的最优值。

以上代码中可以将注释的部分加上就可以了。其中int类型中最小值为0x80000000.

       2:完全背包

此问题来源于hihoCoder中:链接,即将背包只能兑换一次或者不兑换改为可以兑换无数次

/*完全背包问题 http://hihocoder.com/contest/hiho7/problem/1 */
#include <iostream>
#include <memory.h>    //A题时候使用memset需要添加这个库函数
using namespace std;
int main()
{
	int N, M;
	while(cin >> N >> M)
	{
		int need, value;
		int *F = new int[M+1];

		int i, j;
		memset(F, 0, sizeof(int)*(M+1));
		//for(i = 0; i < M+1; i++)
			//F[i] = 0;

		for(i = 1; i < N+1; i++)
		{
			cin >> need >> value;
			for(j = need; j<=M; j++)
			{
				F[j] = F[j] > (F[j-need]+value)?F[j]:(F[j-need]+value);
			}
		}

		// 错误代码
		/*
		如样例 2 10 2 3 3 5
		*/
		/*int k;
		for(i = 1; i < N+1; i++)
		{
			cin >> need >> value;
			for(j = M; j>=need; j--)
			{
				if(need == 0) k = 0;
				else k = j/need;   //
				F[j] = F[j] > (F[j-k*need]+k*value)?F[j]:(F[j-k*need]+k*value);
			}
		}*/
		cout << F[M] << endl;
		delete []F;
	}
}

// 测试输入
/*5 1000
144 990
487 436
210 673
567 58
1056 897*/

// 输出
// 5940
作者:小村长  出处:http://blog.csdn.net/lu597203933 欢迎转载或分享,但请务必声明文章出处。 (新浪微博:小村长zack, 欢迎交流!)
时间: 2024-07-29 20:22:37

0-1背包问题及变种的相关文章

背包问题:0/1背包问题 普通背包问题(贪心算法只适用于普通背包问题)

//sj和vj分别为第j项物品的体积和价值,W是总体积限制. //V[i,j]表示从前i项{u1,u2,…,un}中取出来的装入体积为j的背包的物品的最大价值. 第一种:0/1背包问题 最大化 ,受限于  1)若i=0或j=0,  V[i,j] = 0 2)若j<si, V[i,j] = V[i-1,j] 3)若i>0且j>=si, V[i,j] = Max{V[i-1,j],V[i-1,j-si]+vi} 第二种:背包问题:在选择物品i装入背包时,可以选择物品i的一部分,而不一定要全部

0/1背包问题(回溯法)

回溯法是一个既带有系统性又带有跳跃性的搜索算法.它在包含问题的所有解的解空间树中,按深度优先策略,从根结点出发搜索解空间树.算法搜索至解空间树的任意一结点时,先判断该结点是否包含问题的解.如果肯定不包含,则跳过对该结点为根的子树搜索,逐层向其祖先结点回溯:否则 ,进入该子树,继续按深度优先策略搜索. 问题的解空间 用回溯法解问题时,应明确定义问题的解空间.问题的解空间至少包含问题的一个(最优)解.对于 n=3 时的 0/1 背包问题,可用一棵完全二叉树表示解空间,如图所示: 求解步骤 1)针对所

0/1背包问题的动态规划法求解 —— Java 实现

0/1背包问题的动态规划法求解,前人之述备矣,这里所做的工作,不过是自己根据理解实现了一遍,主要目的还是锻炼思维和编程能力,同时,也是为了增进对动态规划法机制的理解和掌握. 值得提及的一个问题是,在用 JAVA 实现时, 是按算法模型建模,还是用对象模型建模呢? 如果用算法模型,那么 背包的值.重量就直接存入二个数组里:如果用对象模型,则要对背包以及背包问题进行对象建模.思来想去,还是采用了对象模型,尽管心里感觉算法模型似乎更好一些.有时确实就是这样,对象模型虽然现在很主流,但也不是万能的,采用

动态规划算法实现部分——0/1背包问题

代码: import java.util.*; import java.util.Scanner; /* *动态规划思想解决0/1背包问题 */ public class Main{ public static void main(String[] args){ Scanner in=new Scanner(System.in); System.out.println("输入背包的容量"); int bagCap=in.nextInt(); //背包的容量 System.out.pri

动态规划算法求解0,1背包问题

首先我们来看看动态规划的四个步骤: 1. 找出最优解的性质,并且刻画其结构特性: 2. 递归的定义最优解: 3. 以自底向上的方式刻画最优值: 4. 根据计算最优值时候得到的信息,构造最优解 其中改进的动态规划算法:备忘录法,是以自顶向下的方式刻画最优值,对于动态规划方法和备忘录方法,两者的使用情况如下: 一般来讲,当一个问题的所有子问题都至少要解一次时,使用动态规划算法比使用备忘录方法好.此时,动态规划算法没有任何多余的计算.同时,对于许多问题,常常可以利用其规则的表格存取方式,减少动态规划算

动态规划0—1背包问题

动态规划0-1背包问题 ? 问题描写叙述: 给定n种物品和一背包.物品i的重量是wi,其价值为vi,背包的容量为C.问应怎样选择装入背包的物品,使得装 入背包中物品的总价值最大? ? 对于一种物品,要么装入背包,要么不装.所以对于一种物品的装入状态能够取0和1.我们设物品i的装入状态为xi,xi∈ (0,1),此问题称为0-11背包问题. 过程分析 数据:物品个数n=5,物品重量w[n]={0,2,2,6,5,4},物品价值V[n]={0,6,3,5,4,6}, (第0位,置为0,不參与计算,仅

【算法设计与分析】7、0/1背包问题,动态规划

/** * 书本:<算法分析与设计> * 功能:给定n种物品和一个背包,物品i的重量是Wi, 其价值为Vi,问如何选择装入背包的物品,使得装入背包的物品的总价值最大? * 文件:beiBao.cpp * 时间:2014年11月30日19:22:47 * 作者:cutter_point */ #include <iostream> #define SIZEBEIBAO 20 using namespace std; //这个背包问题的最优的子结构是 /* 首先这里一共有m种物品,背包

第十六章 贪心算法——0/1背包问题

1.问题描述: 给定n种物品和一背包.物品i的重量是wi,其价值为vi,背包的容量为C.问:应如何选择装入背包的物品,使得装入背包中物品的总价值最大? 形式化描述:给定c >0, wi >0, vi >0 , 1≤i≤n.要求找一n元向量(x1,x2,…,xn,), xi∈{0,1}, ∋ ∑ wi xi≤c,且∑ vi xi达最大.即一个特殊的整数规划问题. 2.最优性原理: 设(y1,y2,…,yn)是 (3.4.1)的一个最优解.则(y2,…,yn)是下面相应子问题的一个最优解:

动态规划--0,1背包问题(再也不怕类似背包问题了)

这种类型问题三大要素:总重量.每件物品重量.每件物品价值,问最终能够塞进背包中的价值最大是多少?应该怎么选择物品? 当然也不一定是这些,例如上节所说的矿工挖矿:总人数.挖每座矿的人数.每座矿的金子数. 也就是说,只要出现了这三大要素,都可以视为0,1背包问题(物品不可拆分) 动态规划三要素:边界.最优子结构.状态转移方程. 我们一步步进行解析: 初始化:物品总重量:c=8,物品类别:n=['a','b','c','d'],物品重量:w=[2,4,5,3],物品价值:v=[5,4,6,2] 假设我