完全背包问题的分析与优化

完全背包问题跟01背包的区别是01背包每个物品只能选一次,总共就这几个,而完全背包问题是每个物品可以无限选,只要装得下。可以看成是有几种物品,每种都无限多个。

如何根据01背包问题的分析成果来分析完全背包呢?其实很简单,

.01背包在选第i个物品时,容积够用情况下,只有2种状态可选,放还是不放,找出最大价值的选择

而完全背包在选第i种物品时,容积够用情况下,可能有2种以上状态可选,放1个,或者2个,3个,或者不放。找出最大价值的选择

可以利用k = j/weight[i]算出最多可以放几个,然后状态转移方程改为 V[i][j] = max(V[i – 1][j – k*weight[m]] + k * value[i]) 从0到k遍历一遍求出最大值即可

代码如下:

void packageTotal()
{
	for(int i = 1; i <= N; i++)
	{
		for(int j = 1; j <= M; j++)
		{
			if(weight[i] > j)	//如果重量比容量大,放不下,就不放
			{
				V[i][j] = V[i - 1][j];
			}
			else //其实 if跟else可以去掉的
			{
				int k = j / weight[i];	//如果能放下,要看看能放几个,然后放几个跟不放所有情况比一下看哪个价值最大,除法默认向下取,也可以用floor()
				for(int m = 0; m <= k; m++)
				{
					if(V[i - 1][j - m * weight[i]] + m * value[i] > V[i][j])
					{
						V[i][j] = V[i - 1][j - m * weight[i]] + m * value[i];
					}
				}
			}
		}
	}
}

其实if else判断是多余的,通过k的大小就能判断了,更改代码如下:

void packageTotal()
{
	for(int i = 1; i <= N; i++)
	{
		for(int j = 1; j <= M; j++)
		{
			int k = j / weight[i];	//如果能放下,要看看能放几个,然后放几个跟不放所有情况比一下看哪个价值最大
			for(int m = 0; m <= k; m++)
			{
				if(V[i - 1][j - m * weight[i]] + m * value[i] > V[i][j])
				{
					V[i][j] = V[i - 1][j - m * weight[i]] + m * value[i];
				}
			}
		}
	}
}

优化:其实这个跟01背包问题是很像的,每次写入数据都是根据上一行的结果写入的,数组一维就够用了,可以像更改01背包二维至一维那样去改,参考我的01背包问题的博文,

更改如下:

void packageTotal()
{
	for(int i = 1; i <= N; i++)
	{
		for(int j = M; j >= 1; j--)	//跟01背包一样,也是从右向左更新数据,因为更新时候需要上次循环左边的数据
		{
			int k = j / weight[i];	//如果能放下,要看看能放几个,然后放几个跟不放所有情况比一下看哪个价值最大
			for (int m = 0; m <= k; m++)	
			{
				if (V1[j - m * weight[i]] + m * value[i] > V1[j])
				{
					V1[j] = V1[j - m * weight[i]] + m * value[i];
				}
			}		
		}
	}
}

这样就完美了吗?不,还可以继续优化,感觉有时候代码就像艺术品,可以简洁到令人难以置信,它的更改跟01背包的最终优化代码惊人的相似,只是从写入数据时从前往后写了,其它全部不变。

代码如下:

void packageTotal()
{
	for(int i = 1; i <= N; i++ )
	{
		for(int j = 1; j <= M; j++) //相比01背包最终优化代码,只是换了遍历方向而已
		{
			if(j >= weight[i])
			{
				V1[j] = max(V1[j], V1[j - weight[i]] + value[i]);
			}
		}
	}
}

为什么只是换了遍历方向就可以由01背包变成了完全背包呢,这样不是就利用了当前这一次更新数组的左边的数据了吗,上一次01背包为了避免这种情况发生专门从右向左写数据,而这个刚好相反,我这次写数据从左往右写,前面写了以后,留给后面的作参考,因为随着一位数组横向写入数据越多,j也在变大,j变大,就可以看看有没有东西可以继续塞进去,因为01背包一个物品放进去后就不能再用了,而完全背包还可以利用,所以当前这一次对某个物品做出选择后还可以继续选择,也就是说还可以再放第2个该物品,第三个,等等,所以完全背包每次做出选择是取决于当前这一步的,而01背包每次做出选择是取决于上一步的。主要就是因为当前这一步一个物品放过以后,表格逐渐向右填写,随着可放空间的增加,可以判断这一步是否还可以再放一个当前的物品。跟前面那个求出当前可放的最大物品数,然后从0个放到最大个,本质是一样的。

    原文作者: 汉诺塔问题
    原文地址: https://blog.csdn.net/m0_37907835/article/details/78991992
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞