01揹包問題

01揹包

問題描述:有N件商品,第i件商品的重量是weights[i-1],價值是values[i-1],揹包容量是cap
則揹包能夠裝物品的最大價值

首先構建一個二維數組dp,dp[i][j]表示第i件物品時揹包容量爲j時的最大價值。
如何求得dp[i][j]:
(1)values[i-1]>j,說明第i件物品不能放入當前容量爲j的揹包,即不放入第i件商品:
dp[i][j]=dp[i1][j] dp[i][j]=dp[i-1][j]
(2)values[i-1]<=j,則第i件物品可以放入到容量爲j時的揹包中,但是這時候需要考慮放進第i件物品的揹包中物品價值是否比不放時大:
dp[i][j]=max(dp[i1][j],dp[i1][jweights[i1]]+values[i1]) dp[i][j] = max(dp[i-1][j], dp[i-1][j-weights[i-1]]+values[i-1])
max中的第一項表示不放,第二項表示放入第i件,放入第i件就需要把揹包騰出至少weights[i-1]的容量,舉個例子:weights = {3, 4, 3, 2, 5};values = {2, 4, 4, 3, 2};當i=2,j=4時,此時需要放入揹包的物品重量爲4,價值爲4,而dp[i-1][j] = 2,所以需要將第一件重量爲3價值爲2的商品拿出,放入第2件商品,即dp[2][4] = dp[1][0]+4 = 4。

/*
* 01揹包:揹包可裝的最大價值
 * @param 商品重量
 * @param 商品價值
 * @param 揹包容量
 */
public int maxValue(int[] weights, int[] values, int cap) {
	if(cap == 0 || weights.length == 0) {
		return 0;
	}
	if(weights.length != values.length) {
		System.out.println("數組長度不一致");
		return -1;
	}
	int num = weights.length;
	int[][] dp = new int[num+1][cap+1];
	for(int i = 1; i <= num; i++) {
		for(int j = 1; j <= cap; j++) {
			if(j < weights[i-1]) {
				dp[i][j] = dp[i-1][j];
			}
			else {
				dp[i][j] = Math.max(dp[i-1][j], dp[i-1][j-weights[i-1]]+values[i-1]);
			}
		}
	}
	return dp[num][cap];
}

優化

進行優化:
不難看出dp[i][]只和dp[i-1][]有關,因此可以使用兩個一維數組來代替二維數組進行動態規劃的迭代,空間複雜度由O(N*cap)降低爲O(cap)。

public int maxValue(int[] weights, int[] values, int cap) {
	if(cap == 0 || weights.length == 0) {
		return 0;
	}
	if(weights.length != values.length) {
		System.out.println("數組長度不一致");
		return -1;
	}
	int num = weights.length;
	int[] dp1 = new int[cap+1];
	int[] dp2 = new int[cap+1];
	for(int i = 1; i <= num; i++) {
		for(int j = 1; j <= cap; j++) {
			if(j < weights[i-1]) {
				dp2[j] = dp1[j];
			}
			else {
				dp2[j] = Math.max(dp1[j], dp1[j-weights[i-1]]+values[i-1]);
			}
		}
		dp1 = dp2.clone();
	}
	return dp2[cap];
}

實際上還可以進行優化,只使用一個一維數組即可,空間複雜度仍然是O(cap)。不難看出dp2[j]在更新時只和dp1[0…j-1]有關,而不會使用dp1[j…cap],因此可以逆序對dp2進行更新:

int[] dp2 = new int[cap+1];
for(int i = 1; i <= num; i++) {
	for(int j = cap; j >= 1; j--) {
		if(j < weights[i-1]) {
			dp2[j] = dp2[j];
		}
		else {
			dp2[j] = Math.max(dp2[j], dp2[j-weights[i-1]]+values[i-1]);
		}
	}
}
return dp2[cap];

要求恰好裝滿揹包

上述描述不要求恰好裝滿揹包,如果要求恰好裝滿揹包的前提下,求揹包所能裝物品的最大價值,只需要修改初始化條件即可:j = 0時,dp爲0,其他都爲-∞。

int[] dp2 = new int[cap+1];
for(int i = 1; i <= cap; i++) {
	dp2[i] = Integer.MIN_VALUE;
}

如果無法恰好裝滿揹包,則會返回一個很大的負數。
原因:如果要求恰好裝滿,只有容量爲0的時候什麼也沒裝是恰好裝滿的,而其他容量初始時不符合要求,所以應該賦值爲-∞,從下面的例子可以看出,沒有恰好裝滿時,其價值是一個很大的負數。

揹包容量爲10;
int[] weights = {3, 4, 8, 8, 5};
int[] values = {2, 4, 4, 3, 2};  // 沒有合法解
[0, -2147483648, -2147483648, 2, -2147483646, -2147483646, -2147483646, -2147483646, -2147483646, -2147483646, -2147483646]
[0, -2147483648, -2147483648, 2, 4, -2147483644, -2147483644, 6, -2147483642, -2147483642, -2147483642]
[0, -2147483648, -2147483648, 2, 4, -2147483644, -2147483644, 6, 4, -2147483642, -2147483642]
[0, -2147483648, -2147483648, 2, 4, -2147483644, -2147483644, 6, 4, -2147483642, -2147483642]
[0, -2147483648, -2147483648, 2, 4, 2, -2147483644, 6, 4, 6, -2147483642]

int[] weights =  {3, 4, 3, 2, 5};
int[] values = {2, 4, 4, 3, 2};  // 有解,爲10
[0, -2147483648, -2147483648, 2, -2147483646, -2147483646, -2147483646, -2147483646, -2147483646, -2147483646, -2147483646]
[0, -2147483648, -2147483648, 2, 4, -2147483644, -2147483644, 6, -2147483642, -2147483642, -2147483642]
[0, -2147483648, -2147483648, 4, 4, -2147483644, 6, 8, -2147483640, -2147483640, 10]
[0, -2147483648, 3, 4, 4, 7, 7, 8, 9, 11, 10]
[0, -2147483648, 3, 4, 4, 7, 7, 8, 9, 11, 10]
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章