动态规划 背包问题

动态规划 背包问题

问题描述:
有一个背包,总容量为12。有6件物品,每件物品的重量和价值不同,求在背包总容量12的前提下,装进物品的最大价值以及装进物品的编号

单个物品重量和价值:

在这里插入图片描述

为方便进行思考,我们将物品按照重量价值递增,重新排序

在这里插入图片描述


针对此问题,我们可以先列一张表格,标识当前背包容量为j时,前i个物品的最大价值。

在这里插入图片描述

例如X(3,3),代表当背包容量为3时,前3个物品可装入的最大价值。


第一行X(0,0)X(0,12),代表当背包容量为0到12时,前0个物品可装入的最大价值,前0个物品不存在没有价值,即价值恒为0,因此第一行都填入0。

同理第一列X(0,0)X(6,0),代表当背包容量为0时,第0个物品到第6个物品可装入的最大价值,因为背包容量为0,没有物品可以装下,所以第一列都填入0。

在这里插入图片描述


X(1,1) 代表当背包容量为1时,前1个物品的最大价值。由于W(1)=1,刚好可以装入,那么X(i,j)=X(1,1)=V(1)=4 ,因此填入4。并且不论之后背包容量如何扩展,前1个物品的最大价值恒为4。

在这里插入图片描述


当开始第三行,即X(2,j) 填写时,就需要判断背包容量够不够装下当前物品,如果能装下,那装下当前物品和不装当前物品哪个价值最大:

  1. 背包装不下

    当背包容量j小于当前第i个物品重量W(i) 时,即j<W(i) 判断为装不下,此时的最大价值X(i,j) 与前一个X(i-1,j) 的最大价值一样,即X(i,j)=X(i-1,j)

  2. 背包装得下

    当背包容量j大于等于当前第i个物品重量W(i) 时,判断要不要装进背包。

    2.1:装入当前物品时的最大价值Value1
    背包总容量j减去当前物品的重量W(i) 后,获取剩余总容量的上一物品最大价值X(i-1,j-W(i)) 与当前物品的价值V(i) 相加,即 Value1=X(i-1,j-W(i)) + V(i)

    2.2:不装当前物品时的最大价值Value2
    前一个物品i-1在此背包总容量j情景下的最大价值X(i-1,j),即 Value2=X(i-1,j)

Value1-Value2 大于0则取当前和值Value1,反之取上一物品此背包总容量情景下的最大价值Value2

此时不理解上述公式没关系,跟着下面的填数去慢慢理解


X(2,1) 填数,首先判断背包能否装下:

j=1W(2)=2j<W(2),因此当背包容量为1时,物品2装不下,此时最大价值取前一个物品i-1此背包总容量j情景下的最大价值X(i-1,j)=X(2-1,1)=X(1,1)=4,因此X(2,1)=4

在这里插入图片描述


X(2,2) 填数,首先判断背包能否装下:

j=2W(2)=2j=W(2),因此当背包容量为2时,物品2装得下

此时需要判断要不要装物品2:

装入当前物品时的最大价值:Value1 = X(i-1,j-W(i)) + V(i) = X(2-1,2-2) + 1 = X(1,0) + 1 = 0 + 1 = 1

不装当前物品时的最大价值:Value2 = X(i-1,j) = X(2-1,2) = X(1,2) = 4

Value1<Value2,因此取上一物品此背包总容量情景下的最大价值,即X(2,2) = X(1,2) = 4

在这里插入图片描述


X(2,3) 填数,首先判断背包能否装下:

j=3W(2)=2j>W(2),因此当背包容量为3时,物品2装得下

此时需要判断要不要装物品2:

装入当前物品时的最大价值:Value1 = X(i-1,j-W(i)) + V(i) = X(2-1,3-2) + 1 = X(1,1) + 1 = 4 + 1 = 5

不装当前物品时的最大价值:Value2 = X(i-1,j) = X(2-1,3) = X(1,3) = 4

Value1>Value2,因此取当前物品的价值加剩余总容量的最佳价值,即X(2,3) = 5

在这里插入图片描述


按照此规律,我们将表全部填充完毕:

在这里插入图片描述

取两个坐标进行验证:


例如1:X(4,3) 填数,首先判断背包能否装下:

j=3W(4)=3j=W(4),因此当背包容量为3时,物品4装得下

此时需要判断要不要装物品4:

装入当前物品时的最大价值:Value1 = X(i-1,j-W(i)) + V(i) = X(4-1,3-3) + 6 = X(3,0) + 6 = 0 + 6 = 6

不装当前物品时的最大价值:Value2 = X(i-1,j) = X(4-1,3) = X(3,3) = 7

Value1<Value2,因此取上一物品此背包总容量情景下的最大价值,即X(4,3) = X(3,3) = 7


例如2:X(4,4) 填数,首先判断背包能否装下:

j=4W(4)=3j>W(4),因此当背包容量为4时,物品4装得下

此时需要判断要不要装物品4:

装入当前物品时的最大价值:Value1 = X(i-1,j-W(i)) + V(i) = X(4-1,4-3) + 6 = X(3,1) + 6 = 4 + 6 = 10

不装当前物品时的最大价值:Value2 = X(i-1,j) = X(4-1,4) = X(3,4) = 7

Value1>Value2,因此取当前物品的价值加剩余总容量的最佳价值,即X(4,3) = 10


代码实现

int bag = 12; // 背包总容量
int number = 6; // 物品数量
int[] weight = {1, 2, 2, 3, 4, 6}; // 物品重量数组
int[] value = {4, 1, 3, 6, 8, 2}; // 物品价值数组
int[][] max = new int[number + 1][bag + 1]; // 最大价值数组

int value1 = 0; // 过渡值:装入当前物品时的最大价值
int value2 = 0; // 过渡值:不装当前物品时的最大价值

// 找出最大价值

for (int i = 1; i <= number; i++) { // 行
	for (int j = 1; j <= bag; j++) { // 列
		// 判断背包能否装得下
		if (j < weight[i - 1]) { // 装不下
			max[i][j] = max[i - 1][j]; // 与前一个物品的最大价值相同
		} else { // 能装下
			// 前一个物品剩余重量的最大价值+当前物品的价值
			value1 = max[i - 1][j - weight[i - 1]] + value[i - 1];
			// 前一个物品当前重量的最大价值
			value2 = max[i - 1][j];
			// 取最大值
			max[i][j] = value1 > value2 ? value1 : value2;
		}
	}
}

System.out.println(Arrays.deepToString(max)); // 打印最大价值数组
System.out.println("MaxValue = " + max[number][bag]); // 打印最大价值

结果输出:

[[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 
[0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4], 
[0, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5], 
[0, 4, 4, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8], 
[0, 4, 4, 7, 10, 10, 13, 13, 14, 14, 14, 14, 14], 
[0, 4, 4, 7, 10, 12, 13, 15, 18, 18, 21, 21, 22], 
[0, 4, 4, 7, 10, 12, 13, 15, 18, 18, 21, 21, 22]]
MaxValue = 22

在上述结果基础上,如果需要知道最大价值情况下,都装入了哪些商品,就需要进行回溯。

在这里插入图片描述

已知X[6][12] 为最大价值,首先判断它有没有装入背包,即X[6][12] = X[6-1][12] 是否生效,若相等,则未装入,继续以X[6-1][12] 作为最大价值进行判断。

X[5][12] 同理与X[5-1][12] 比较,发现不相等,通过公式22 = X[5-1][j-W(5)] + V[5] = X[4][12-4] + 8 = X[4][8] + 8 ,即X[4][8] = 22-8 = 14,反算后值也相等,则第5个物品装入了背包。

以此类推,公式为:

  1. 未装入背包
    X[i][j] = X[i-1][j],则当前物品未装入背包。

  2. 转入背包
    X[i-1][j-W(i)] = X[i][j] - V[i],则当前物品装入背包。


代码实现:


List list = new ArrayList<>(); // 装入物品编号

// 回溯查找装入物品编号
findNo(number, bag);
Collections.sort(list); // 按照物品编号排序
System.out.println(list.toString());

void findNo(int i, int j) {
	if (i > 0) {
		// 判断是否装入背包
		if (max[i][j] == max[i - 1][j]) {
			// 与上一物品最大价值相等,当前物品未装入
			findNo(i - 1, j);
		} else if (j - weight[i-1] >= 0 && (max[i - 1][j - weight[i-1]] == max[i][j] - value[i-1])) {  
			// 与上一物品最大价值不相等且反算成功,当前物品装入,继续递归
			list.add(i);
			findNo(i - 1, j - weight[i-1]);
		}
	}
}

结果输出:

[1, 2, 3, 4, 5]
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值