01背包问题的两种解法

引言

“01背包问题”是一个比较基础的算法问题,它可以衍生为其他相对复杂的算法问题,比如“装箱问题”。通过对“01背包问题”的学习与破解,我们可以掌握一些比较常见的算法,配养一定的算法思维以及解题能力。
“01背包问题”是学习计算机相关专业的学生必须掌握的经典算法问题之一。

1、“01背包问题”的问题雏形

01背包是在M件物品取出若干件放在空间为W的背包里,每件物品的体积为W1,W2至Wn,与之相对应的价值为P1,P2至Pn。
01背包是背包问题中最简单的问题。01背包的约束条件是给定几种物品,每种物品有且只有一个,并且有权值和体积两个属性。在01背包问题中,因为每种物品只有一个,对于每个物品只需要考虑选与不选两种情况。
如果不选择将其放入背包中,则不需要处理。如果选择将其放入背包中,由于不清楚之前放入的物品占据了多大的空间,需要枚举将这个物品放入背包后可能占据背包空间的所有情况。

2、递归算法解“01背包问题”

2.1 算法分析

01背包问题与部分背包问题很类似,但是部分背包问题是物品是可以进行分割的,假如不能够拿取当前的物品,那么可以拿取这个物品的某一部分来构成最大质量并且价值是最大的。
但是01背包问题中物品是不可以进行分割的,可以选择拿取当前的物品也可以选择不拿这两种情况,所以这个是两个问题的区别。
我们先不考虑怎么样写计算机程序,先是看自己的直觉思维,怎么样拿取物品才能够使拿取的物品最终的价值最大但是不超过最大质量。
设现有4个质量w分别为2、1、3、2,价值v分别为3、2、4、2的物品,要放入承重总量为W的背包中。
首先我们可能考虑先凑成目标的最大质量,W = 5 那么我们可以由多个组合来拿取,可以拿取0,1, 3号物品,0,2号物品,也可以拿取2,3号物品,再在这几个组合中找出价值最大的,可以发现0,1, 3号物品和2,3号物品的价值是最大的。
但是除了可以凑成目标最大质量情况有可能价值最大,也可能是这样的情况:不能够凑成最大质量,但是它的总价值有可能是最大的。
所以,我们可以使用递归的方式来进行解决,在所有的可能中找出一个能够不超过目标质量但是价值是最大的,找出所有可能这实际上也是一种深度优先搜索的过程。
我们在遇到问题的时候要先进行给出测试样例的分析,利用简单的推导,类比,找出问题的相似性来进行问题的求解。
下面是使用普通递归的方法来进行解决(也可以叫做深搜来解决:实际上就是深搜)。
其中递归有两个变量在进行变化一个是可以选择的当前物品的下标,一个是可以选择的当前物品的最大质量,所以在方法中传入两个参数来,还有其他的不变的参数也可以传递进来方便操作,也可以将这些设置为全局变量不用传递进来都可以。

2.2 算法描述

要解决01背包问题,就必须要做对比。
首先我们要确定递归的出口,当物品数量为0的时候,或者当书包容量为0,就不能再装货物了,这就是递归的出口。
接下来,我们要判断第i件物品的重量是不是大于当前的物品重量,如果大于,则不挑选该物品,小于则挑选,继续向下进行递归,直至满足递归出口条件。

2.3 代码实现

import java.util.*;

public class Main{
	/**
	 * 
	 * @param i 第i件物品
	 * @param j 书包的重量
	 * @param V 物品的价值
	 * @param W 物品的重量
	 * @return
	 */
	public static int dfs(int i, int j, int[] W, int[] V) {
		// 检查物品个数是否小于等于0,书包容量是否足够容纳第i件物品
		if (i <= 0 || j == 0) {
			return 0;
		}
		if (W[i] > j) {
			return dfs(i - 1, j, W, V);
		}
		int value, value2 = 0;
		// 不选择第i件物品,则递归挑选除去第i件物品的剩余物品
		value = dfs(i - 1, j, W, V);
		// 选择第i件物品,同时要减去对映的背包空间,且要加上物品i的价格V[i]
		value2 = dfs(i - 1, j - W[i], W, V) + V[i];
		// 如果不选择第i件物品的递归返回结果大于选择第i件的递归返回结果,则返回不选择第i件的结果
		if (value > value2) {
			return value;
		}
		// 反之返回选择第i件物品的结果
		return value2;
	}

	public static void main(String[] args) {
		// 物品金额,0相当于开头位置的作用,规范数组下标
		int[] V = { 0, 3, 4, 5, 6, 10 };
		// 物品的总重量,0相当于开头位置的作用,规范数组下标
		int[] W = { 0, 2, 3, 4, 5, 9 };
		// 假设书包容量是20
		System.out.println(dfs(5, 20, W, V));
	}
}

3、动态规划法解“01背包问题”

3.1 算法分析

动态规划与分治法类似,都是把大问题拆分成小问题,通过寻找大问题与小问题的递推关系,解决一个个小问题,最终达到解决原问题的效果。
但不同的是,分治法在子问题和子子问题等上被重复计算了很多次,而动态规划则具有记忆性,通过填写表把所有已经解决的子问题答案纪录下来,在新问题里需要用到的子问题可以直接提取,避免了重复计算,从而节约了时间,所以在问题满足最优性原理之后,用动态规划解决问题的核心就在于填表,表填写完毕,最优解也就找到。

3.2 算法描述

声明一个大小为m[n][c]的二维数组,m[i][j]表示:在面对第i件物品,且背包容量为j时所能获得的最大价值。那么我们可以很容易分析得出 m[i][j] 的计算方法:
(1)j < w[i]的情况,这时候背包容量不足以放下第i件物品,只能选择不拿:m[i][j] = m[i-1][j]
(2)j>=w[i] 的情况,这时背包容量可以放下第i件物品,我们就要考虑拿这件物品是否能获取更大的价值。
如果拿取,m[i][j] = m[i-1][j-w[i]] + v[i]。 这里的m[i-1][j-w[i]]指的就是考虑了i-1件物品,背包容量为j-w[i]时的最大价值,也是相当于为第i件物品腾出了w[i]的空间。
如果不拿,m[i][j] = m[i-1][j] , 同(1)
究竟是拿还是不拿,自然是比较这两种情况那种价值最大。
由此可以得到状态转移方程:

if(j>=w[i]){
m[i][j]=max(m[i-1][j],m[i-1][j-w[i]]+v[i]);}
else{
m[i][j]=m[i-1][j];}

3.3 代码实现

 import java.util.*; 
	/**
	 * 运用动态规划解决
	 *
	 */
	public class Main {
	public static void main(String[] args) {
	     //背包总重量
	     int bag_weight=20;
	     //物品个数
	     int shopping_num=5;
	     //物品金额
	     int []V={0,3,4,5,6,10};
	     //物品的重量
	     int []W={0,2,3,4,5,9};
	     //定义背包容量与物品个数的状态数组 bag[i][j];i表示当前有i件,j表示当前的容量
	     int [][]bag=new int[shopping_num+1][bag_weight+1];
	     //依次选择第i件物品
	     for(int i=1;i<=shopping_num;i++){
	         //依次的增加背包容量数目
	         for(int j=1;j<=bag_weight;j++){
	             if(i>0&&W[i]<=j){
	                 bag[i][j]=(bag[i-1][j]>bag[i-1][j-W[i]]+V[i]?bag[i-1][j]:bag[i-1][j-W[i]]+V[i]);
	             }
	             else{
	                 bag[i][j]=bag[i-1][j];
	             }
	         }
	     }
	     System.out.println(bag[shopping_num][bag_weight]);	     
	}
}


posted @ 2021-05-07 20:55  凤青  阅读(1744)  评论(0编辑  收藏  举报