0-1背包、完全背包、多重背包问题求解

背包问题无外乎是利用动态规划的思想求解。而求解动态规划问题最主要是寻找到核心推导式(有些问题隐藏的非常深,一眼很难看出来)。这里不再介绍动态规划的思想,主要讲解背包问题,闲话少说,直接上题。

1、0-1背包

问题描述:有编号分别为a,b,c,d,e的五件物品,它们的重量weight分别是2,2,6,5,4,它们的价值value分别是6,3,5,4,6,每件物品数量只有一个,现在给你个承重为C=10的背包,如何让背包里装入的物品具有最大的价值总和?

利用动态规划思想:首先生成一个N*(C+1)的二维数组,N是物品数,C是背包容量。

i、设weight[i]为第i+1件物品的体积,value[i]为第i+1件物品产生的价值

ii、其dp[i][w]的含义是只考虑前i+1件物品(无所谓是否在背包中)在背包容量为w下,可获得的最大价值收益。

寻找其核心式:dp[i][w]=max{dp[i-1][w],dp[i-1][w-weight[i]]+value[i]},其含义如下:

a、首先分析dp[i-1][w-weight[i]]+value[i],考虑前i件物品下,再添加第i+1件物品(确保总体积小于w)产生的总价值;

b、其次分析dp[i-1][w],考虑前i件物品在体积w下,产生的总价值;

c、最后取两者的最大值作为考虑前i+1件物品(无所谓是否全在背包中)产生的总价值。

name weight value 0 1 2 3 4 5 6 7 8 9 10
a 2 6 0 0 6 6 6 6 6 6 6 6 6
b 2 3 0 0 6 6 9 9 9 9 9 9 9
c 6 5 0 0 6 6 9 9 9 9 11 11 14
d 6 4 0 0 6 6 9 9 9 10 11 13 14
e 4 6 0 0 6 6 9 9 12 12 15 15 15
#include<vector>
#include<algorithm>

using namespace std;

int knapsack0_1(vector<int>& value, vector<int>& weight, int& C) {
	int row = value.size();
	vector<vector<int>> dp(row, vector<int>(C + 1, 0));
	
	//先单独考虑第1件物品是否存入背包中,对dp的第一行进行赋值
	for (int w = 0; w < C + 1; ++w) {
		if (w >= weight[0])
			dp[0][w] = value[0];
	}
	//再考虑前i件物品是否存入背包
	for (int i = 1; i < row; ++i) {
		for (int w = 0; w < C + 1; ++w) {
			if (w - weight[i] >= 0) {
				dp[i][w] = max(dp[i - 1][w], dp[i - 1][w - weight[i]] + value[i]);
			}
			else
				dp[i][w] = dp[i - 1][w];
		}
	}
	return dp[row-1][C];
}

2、完全背包(即每件物品数无限个,这是与0-1背包问题的最大不同)

问题描述:有编号分别为a,b,c,d的四件物品,它们的重量weight分别是2,3,4,7,它们的价值value分别是1,3,5,9,每件物品数量无限个,现在给你个承重C为10的背包,如何让背包里装入的物品具有最大的价值总和?

解法与01背包问题类似,只是在对二维数组dp的初始化及递推公式上需要稍微变化一下。

对二维数组dp的第一行初始化时,dp[0][w]=w<weight[0]?0:(w/weight[0]*value[0]);

推导式:dp[i][w]=max{dp[i-1][w],dp[i][w-weight[i]]+value[i]},注意这里应当考虑放入一个物品 i 时应当考虑还可能继续放入 i,因此这里是dp[i][w-weight[i]]+value[i], 而不是dp[i-1][w-weight[i]]+value[i]。

name weight value 0 1 2 3 4 5 6 7 8 9 10
a 2 1 0 0 1 1 2 2 3 3 4 4 5
b 3 3 0 0 1 3 3 4 6 6 7 9 9
c 4 5 0 0 1 3 5 5 6 8 10 10 11
d 7 9 0 0 1 3 5 5 6 9 10 10 12
int knapsackAll(vector<int>& value, vector<int>& weight, int& C) {
	int row = value.size();
	vector<vector<int>> dp(row, vector<int>(C + 1, 0));

	//先单独考虑第1物品a是否存入背包中,对dp的第一行进行赋值
	for (int w = 0; w < C + 1; ++w) {
			dp[0][w] = w<weight[0]?0:w/weight[0]*value[0];
	}
	//再考虑前i件物品是否存入背包(存在着数量无限次)
	for (int i = 1; i < row; ++i) {
		for (int w = 0; w < C + 1; ++w) {
			if (w - weight[i] >= 0) {
				dp[i][w] = max(dp[i-1][w], dp[i][w - weight[i]] + value[i]);
			}
			else
				dp[i][w] = dp[i - 1][w];
		}
	}
	return dp[row - 1][C];
}

3、多重背包问题(给定了每件物品的数量:多重背包中每个物品的个数都是给定的,可能不是一个,绝对不是无限个。)

问题描述:有编号分别为a,b,c的三件物品,它们的重量weight分别是1,2,2,它们的价值value分别是6,10,20,他们的数目count分别是10,5,2,现在给你个承重C为 8 的背包,如何让背包里装入的物品具有最大的价值总和?

解法一:

对二维数组dp的第一行初始化时,当w/weight<=count[0]时,dp[0][w]=w<weight[0]?0:(w/weight[0]*value[0]),当w/weight>count[0]时,dp[0][w]=count[0]*value[0]

推导式:dp[i][w]=max{dp[i-1][w],dp[i-1][w-k*weight[i]]+k*value[i]},其中dp[i-1][w]是指第i件物品要么一件也不放置,要么第 i 件物品放 k 件,其中 1 <= k <= (w/weight[i])&&k<=count[i],考虑这一共 k+1 种情况取其中的最大价值即为dp[i][w]的值。两者取其中最大值。这里为什么不能像完全背包一样直接考虑dp[i][w-weight[i]]+value[i]呢?因为这样不容易判断第 i 件物品的个数是否超过限制数量 count[i]。

name weight value count 0 1 2 3 4 5 6 7 8
a 1 6 10 0 6 12 18 24 30 36 42 48
b 2 10 5 0 6 12 18 24 30 36 42 48
c 2 20 2 0 6 20 26 40 46 52 58 64
int knacksackNotAll(vector<int>& value, vector<int>& weight, vector<int>& count, int& C) {
	int row = value.size();
	vector<vector<int>> dp(row, vector<int>(C + 1, 0));

	//先单独考虑第1物品a是否存入背包中,对dp的第一行进行赋值
	for (int w = 0; w < C + 1; ++w) {
		if (w / weight[0] <= count[0])//对物品数有限制
			dp[0][w] = w < weight[0] ? 0 : (w / weight[0] * value[0]);
		else
			dp[0][w] = count[0]*value[0];
	}

	//再考虑前i件物品是否存入背包(存在着数量限制次数问题)
	for (int i = 0; i < row; ++i) {
		for (int w = 0; w < C + 1; w++) {
			if (w >= weight[i]) {
				int k = 0;
				while (w >= (k+1)*weight[i]&&(k+1)<=count[i])//此条件限制了1<=k<=w/weight[i] && k<=count[i]
					k++;
				dp[i][w] = max(dp[i - 1][w], dp[i-1][w - k*weight[i]] + k*value[i]);
			}
			else {
				dp[i][w] = dp[i - 1][w];
			}
		}
	}
	return dp[row - 1][C];
}

解法二:

由于0-1背包的物品可以是相同的,于是多重背包问题可以拆成多个物品,看成是多个物品的0-1背包问题。如上题题干,可以转换成有10+5+2=17件物品,其重量分别是1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2。其物品对应价值分别是6,6,6,6,6,6,6,6,6,6,10,10,10,10,10,20,20。承重背包为8,求装入物品具有最大价值的总和?

//先写出0-1背包问题求解函数
int knapsack0_1(vector<int>& value, vector<int>& weight, int& C) {
	int row = value.size();
	vector<vector<int>> dp(row, vector<int>(C + 1, 0));

	//先单独考虑第1件物品是否存入背包中,对dp的第一行进行赋值
	for (int w = 0; w < C + 1; ++w) {
		if (w >= weight[0])
			dp[0][w] = value[0];
	}
	//再考虑前i件物品是否存入背包
	for (int i = 1; i < row; ++i) {
		for (int w = 0; w < C + 1; ++w) {
			if (w - weight[i] >= 0) {
				dp[i][w] = max(dp[i - 1][w], dp[i - 1][w - weight[i]] + value[i]);
			}
			else
				dp[i][w] = dp[i - 1][w];
		}
	}
	return dp[row - 1][C];
}

//将多重背包问题转换成0-1背包问题,再利用0-1背包函数求解
int knapsackNotAllOtherWay(vector<int>& value, vector<int>& weight, vector<int>& count, int& C) {
	vector<int> value1;
	vector<int> weight1;
	for (int i = 0; i < count.size(); ++i) {
		for (int j = 0; j < count[i]; ++j) {
			value1.push_back(value[i]);
			weight1.push_back(weight[i]);
		}
	}
	return knapsack0_1(value1, weight1, C);//从而转换成求解0-1背包问题
}

参考链接:

1、https://blog.csdn.net/na_beginning/article/details/62884939

2、http://www.cnblogs.com/fengty90/p/3768845.html

3、http://blog.csdn.net/mu399/article/details/7722810

4、http://blog.csdn.net/xiaowei_cqu/article/details/8191808

5、http://blog.csdn.net/insistgogo/article/details/11176693

猜你喜欢

转载自blog.csdn.net/TT_love9527/article/details/81564650