动态算法规划算法背包问题

01背包:

问题描述:

现在有4个货物,表示成数值对的形式(重量w,价值v)分别为(2,3);(1,2);(3,4);(2,2)

如果现在有一个人只能拿得动质量为5的东西,怎么选择可以在能力范围内拿到最大价值的货物。


问题解析:

绘制一个2D表格,横向表示能力值上限(最大重量总和),纵向向下依次取第0,1,2,3,4件货物,经过决策后的最优结果dq记录在表格中:

1、易知,无论是最大重量还是货物为0时,最后的价值只能是0,即表格的第一行和第一列都是0;

2、从第一件货物开始,假设最大重量变为1,对第一件货物(2,3)而言,还是无能为力,因此价值为0;

 假设最大重量变为2,对第一件货物(2,3)而言,可以取走,价值变为3;

 假设最大重量变为3,4,5,对第一件货物(2,3)而言,都可以取走,价值变为3;

则得到第一列,若果只有第一件货物能决策到的最优情况表。

动态算法规划算法背包问题_第1张图片

3、接下来判断第二件货物:

      假设最大重量变为1,对第二件货物(1,2)而言,可以尝试;如果决定取走,则占去1个体力点,而在1-1=0个体力点在上一行的最优价值为0,加入后0+2=2个价值点。如果保持为第一个货物,不取第二件货物,只有表中[1,1]位置的0;显然,取走更好,所以[2,1]位置填2;

      假设最大重量变为2,对第二件货物(1,2)而言,可以尝试;如果决定取走,则占去1个体力点,而在2-1=1个体力点在上一行的最优价值为0,加入后0+2=2个价值点。如果保持为第一个货物,不取第二件货物,有表中[1,2]位置的3;即,加入第二件货物的情况下,加上剩余体力点在第一列中的对应最优值的综合反倒不如不取第二件货物只保留第一件货物来的价值大。这种情况下,不取走保持第一列结果(对第一个货物的处理结果)就好,所以[2,2]位置填3;

       假设最大重量变为3,对第二件货物(1,2)而言,可以尝试;如果决定取走,则占去1个体力点,而在3-1=2个体力点在上一行的最优价值为3,加入后3+2=5个价值点。如果保持为第一个货物,不取第二件货物,只有表中[1,3]位置的3;显然,取走更好,所以[2,3]位置填更新值5;

      假设最大重量变为4,对第二件货物(1,2)而言,可以尝试;如果决定取走,则占去1个体力点,而在4-1=3个体力点在上一行的最优价值为3,加入后3+2=5个价值点。如果保持为第一个货物,不取第二件货物,只有表中[1,4]位置的3;显然,取走更好,所以[2,4]位置填更新值5;

     假设最大重量变为5,对第二件货物(1,2)而言,可以尝试;如果决定取走,则占去1个体力点,而在5-1=4个体力点在上一行的最优价值为3,加入后3+2=5个价值点。如果保持为第一个货物,不取第二件货物,只有表中[1,5]位置的3;显然,取走更好,所以[2,5]位置填更新值5;

则得到第二列,若果只有一二件货物能决策到的最优情况表。

动态算法规划算法背包问题_第2张图片

4、参考步骤3判断依次添加至第4件货物得到:

动态算法规划算法背包问题_第3张图片

5、取题目中货物数量=4,最大重量=5时,对应的最大价值7。

在过程中发现递推公式如下:

     1、dp[i][0] = dq[0][j]=0;

     2、for i=1:N

            for j=w[i]:W

                dp[i][j] = max{dp[i-1][j], dp[i-1][j-w[i]]+v[i]};

            end

           end    

则函数代码如下:
public static int bagSel_2D(int[][] bags,int W) {
		
	int[][] dp = new int[bags.length+1][W+1];
	for(int i = 0;idp[i-1][j-bags[i-1][0]]+bags[i-1][1])
				//如果保持不变的情况下优于后一种;则保持不变
		       	    //后一种:如果仅有总容量去掉当前袋cost的总容量下前i-1袋的最佳情况,放入此袋后的总value
				dp[i][j] = dp[i-1][j];  
				else  //否则
					dp[i][j] = dp[i-1][j-bags[i-1][0]]+bags[i-1][1];
		}
	}
	//输出放入总背包的袋子
	int temp = W;
	for(int i = bags.length;i>0;i--) {
		if(dp[i-1][temp]

上图2D表中、算法的时间复杂度和空间复杂度都为N*M。

其实每一行的操作是相同的,所以其实只用一行数组就可以进行迭代。但是, dp[i][j] = max{dp[i-1][j], dp[i-1][j-w[i]]+v[i]}需要参考上一列的处理结果,如果依然按照for j=1:W的顺序进行操作会导致 小于j的部分被覆盖掉二无法参考;在2D表中,已经进行过说明,在2D表中,从W:1其实和从1:W处理没有差别。但在1D数组里,从W:1可以避免上一列小于j的数据被覆盖掉,所以应该从W:1进行计算。得到伪代码如下:

for i=1:N

            for j=W:-1:w[i]

                dp[j] = max{dp[j], dp[j-w[i]]+v[i]};

            end

end

java实现函数如下:

public static int bagSel_1D(int[][] bags,int W) {
		int[] dp = new int[W+1];
		for(int i = 0;i=bags[i][0];j--)
				if(dp[j]



01背包恰好装满问题:

以上结果是不要求完全用完最大重量,若要求恰好用完最大重量时,直接将1D实现中的初始化修改为

dp[0]=0;dp[1:W+1]=-∞,就是针对恰好装满的情况。

原因(勉强解释):当决策第0件货物(0,0)时,只有j=0时是恰好装满的最佳状态,而其他状态并无法满足,在下面列的决策中也不希望其成为最优决策的前一步决策,所以设为-∞。



完全背包问题:

在完全背包问题中,货物可以重复使用。在上面1D数组中的揭发中,前面数据的覆盖对于01背包问题是不适用的,但是对于完全背包问题,对于覆盖数据的使用,世界上就是背包的重复使用,所以只要将01背包问题中的1D数据的解法中的决策顺序转为j=w(i):W+1就是完全背包问题的解法。

代码如下:

public static int bagInfiniteSel(int[][] bags,int W) {
		int[] dp = new int[W+1];
		for(int i = 0;i



测试用例:

public static void main(String[] args) {
	int[][] bags = {{2,3},{1,2},{3,4},{2,2}};
	int sumCost = 5;
	System.out.println(bagSel_2D(bags,sumCost));
	System.out.println(bagSel_1D(bags,sumCost));
	System.out.println(bagSel_Full_1D(bags,sumCost));
	System.out.println(bagInfiniteSel(bags,sumCost));
}

参考:

https://blog.csdn.net/ls5718/article/details/52227908


你可能感兴趣的:(Dynamic,programming)