0-1背包问题的两种动态规划算法思想

/*
  Name: 0-1背包问题的两种动态规划算法思想 
  Copyright: 
  Author: 巧若拙 
  Date: 07-03-17 15:30
  Description: 
给定n中物品和一个容量为c的背包,物品i的重量为Wi,其价值为Vi,
0-1背包问题是如何选择装入背包的物品(物品不可分割),使得装入背包的物品的价值为最大。

1.题目分析:
考虑到每种物品只有2种选择,即装入背包或不装入背包,并且物品数和背包容量已给定,
要计算装入背包物品的最大价值和最优装入方案,可用动态规划算法思想。

2.算法设计:
a. 物品有n种,背包容量为C,分别用p[i]和w[i]存储第i种物品的价值和重量,用
x[i]标记第i种物品是否装入背包,用数组p[n][c]记录给定n个物品装入容量为c的背包的最大价值。 

动态规划有两种基本思路,一种是自顶而下的备忘录算法,它采用递归的方式,一步步缩小问题的规模,
找到边界(n==0)以后,处理好边界(bestP = (c >= W[n]) ? P[n] : 0;),然后一步步返回,返回更大问题的解。
每获得一个子问题的答案,便保存到备忘录数组p[n][c],避免下次充分计算,提升了效率。
另一种思路是自底向上的动态规划算法,它从最小的子问题(只包含0号物品)开始处理,然后依次增加可装物品的数量,
用数组p[n][c]记录每一个已处理子问题的答案,由于0-1背包问题具有最优子结构,
每个问题的最优解都是由其子问题的最优解组成,故这样依次递增问题的规模,可以得到最终需要的最优解。 
 
3. 复杂度分析:
无论是自顶向下的备忘录算法还是自底向上的动态规划算法,都可以在O(n^3)时间内求解。
他们有O(n^2)个备忘记录项(或子问题),这些记录项的初始化耗费O(n^2)时间。
每个记录项只填入一次,每次填入时,耗费O(n)时间。因而填入O(n^2)各记录项总共耗费O(n^3)时间。
对每个子问题,两种方法都只解一次,并记录答案,再碰到该子问题时,不重新求解而是取用已有答案。
它们节省了计算量,提高了算法的效率。自底向上的动态规划算法按顺序计算了每个子问题的解;
自顶向下的备忘录算法采用递归的方式只计算了确实需要求解的子问题,效率更高(当然,由于需要调用递归函数,
有了一些额外的开销),也更便于理解。 
 
*/
#include<iostream>
#include<cmath>

using namespace std;

const int CMAX = 40; //背包最大容量 
const int N = 4; //物品的个数
int W[N] = {3, 1, 2, 5};//物品的重量 
int P[N] = {5, 2, 4, 10};//物品的价值 
int X[N]; //解向量
int B[N][CMAX+1]; //备忘录,记录给定n个物品装入容量为c的背包的最大价值 
int sum = 0;

int Best(int n, int c); //备忘录:自顶而下,获得给定n个物品装入容量为c的背包的最大价值 
int Best_2(int n, int c);//动态规划:自底而上,获得给定n个物品装入容量为c的背包的最大价值 
int Max(int a, int b);

int main() 
{
 	int c = 6; //背包容量 
 	
 	for (int i=0; i<N; i++)//初始化为-1,表示还没有存储该备忘录 
 	{
	 	for (int j=1; j<=c; j++)
	 	{
		 	B[i][j] = -1;
		}
	}
    
//	int bestp = Best(N-1, c); 
	int bestp = Best_2(N-1, c); 
	
	for (int i=0; i<N; i++)
 	{
	 	for (int j=1; j<=c; j++)
	 	{
		 	cout << "B[" << i << "][" << j << "] = " << B[i][j] << " ";
		}
		cout << endl; 
	}
	
	int bestw = 0;
    for (int i=N-1; i>0; i--)
	{
	 	if (B[i][c] == B[i-1][c])//不装物品i 
	 	{
	        X[i] = 0;
	    }
	    else
	    {
		 	X[i] = 1;
		 	bestw += W[i];
		 	c -= W[i];
		}
	}
	X[0] = (B[0][c] > 0) ? 1 : 0; //是否装第0个物品
	bestw += (X[0] == 0) ? 0 : W[0];
	
	cout << "背包的最大价值:" << bestp << "(" << bestw << ")" << endl;
    cout << "背包的最优解:";
    for (int i=0; i<N; i++)
	{
	 	cout << X[i] << " ";
	}
	cout << endl; 
   
    system("pause");
    return 0;
}

int Max(int a, int b)
{
 	return (a > b) ? a : b;
}

int Best(int n, int c)//备忘录:自顶而下,获得给定n个物品装入容量为c的背包的最大价值 
{
 	if (B[n][c] != -1)  //如果这个问题曾经计算过,直接返回 
 	{
		return B[n][c];
	}
	
	int bestP = 0;
	if (n == 0)//处理第0个物品,即只有一个物品 
	{
		bestP = (c >= W[n]) ? P[n] : 0;
	}
	else
	{
		bestP = Best(n-1, c); //先计算不装第n个物品的情形 
		if (c >= W[n])//如果装得下,从装和不装两者中去最大值 
		{
		    bestP = Max(bestP, Best(n-1, c-W[n])+P[n]);
		}
	}
 	
	B[n][c] = bestP;//做备忘录 
	
	cout << (++sum) << ": " << "B[" << n << "][" << c << "] = " << B[n][c] << endl;
    return bestP;
}

int Best_2(int n, int c)//动态规划:自底而上,获得给定n个物品装入容量为c的背包的最大价值 
{
	//记录第0个物品装入容量为0-c的背包的最大价值 
 	int jMax = (c < W[0]-1) ? c : W[0]-1;
	for (int j=0; j<=jMax; j++)
	{
		B[0][j] = 0;
	}
	for (int j=W[0]; j<=c; j++)
	{
		B[0][j] = P[0];
	}
	
	//记录前i(i>=1)个物品装入容量为0-c的背包的最大价值 
 	for (int i=1; i<n; i++)
	{
		jMax = (c < W[i]-1) ? c : W[i]-1;
		for (int j=0; j<=jMax; j++)
		{
			B[i][j] = B[i-1][j]; 
		}
		for (int j=W[i]; j<=c; j++)
		{
			B[i][j] = (B[i-1][j] > B[i-1][j-W[i]]+P[i]) ? B[i-1][j] : B[i-1][j-W[i]]+P[i];
		}
	}
	//第n个物品只需考虑容量为c的一种情况
	B[n][c] = B[n-1][c];
	if (c >= W[n])
	{
		B[n][c] = (B[n-1][c] > B[n-1][c-W[n]]+P[n]) ? B[n-1][c] : B[n-1][c-W[n]]+P[n];
	}
	
	return B[n][c];
}

    原文作者:动态规划
    原文地址: https://blog.csdn.net/QiaoRuoZhuo/article/details/64904441
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞