0-1背包问题的动态规划解法

【0-1背包问题描述】

给定n种物品和一个背包。物品i的重量是w[i],其价值为v[i],背包容量为c。问应如何选择装入背包中的物品,使得装入背包中物品的总价值最大?在选择装入背包中的物品时,对每种物品i只有两种选择,即装入或者不装入背包。不能将物品i装入背包多次,也不能只装入物品i的部分。因此,该问题被称为0-1背包问题。

【0-1背包动态规划解法思路】

动态规划算法适用于解最优化问题,并且我们在思考问题的时候常常是自顶向下的分析问题,自底向上求解。通常按照以下4步骤设计:

  1. 找出最优解的性质,并刻画其结构特征
  2. 递归地定义最优值
  3. 以自底向上的方式计算出最优值
  4. 根据计算最优值时得到的信息,构造最优解

对于0-1背包问题来说,我们首先来刻画问题,描述最优值。即设m(n,c)为考虑将1到n个物品装入容量为c的背包中的最大价值。其次我们来自顶向下地分析问题,虑最后一个物品的装载情况:由于0-1背包问题的特性,最后一个物品只有两种装载状态,即要么将物品n装入背包,要么将物品n丢弃,不装入背包。而这两种情况对应的条件分别为:如果第n个物品的重量比现有背包的剩余容量大的话,则物品n无法再装入背包,此时的最大价值m[n][c] = m[n-1][c]。如果第n个物品的重量比现有背包的剩余容量小的话,则物品n可以再装入背包,此时的最大价值取决于第n个物品的价值是否加入后导致总价值减少,即此时的最大价值m[n][c]为m[n-1][c]和m[n-1][c-w[n]]+v[n]这两个值的最大值。

【0-1背包动态规划状态转移方程及初始状态值】

在求解状态转移方程时,将问题一般化处理,即设m[i][j]为考虑将第1个物品到第i个物品的装载状态,将其装入当前容量为j的背包中的最大价值,则状态转移方程如下:

if(w[i] > j)
    m[i][j] = m[i-1][j]
if(w[i] <= j)
    Max{m[i-1][j],m[i-1][j-w[i]]+v[i]}

其中i的取值范围为1<=i<=n,1<=j<=c。

再考虑0-1背包问题的边界条件,显然当i为0时,代表当前没有物品可以放入背包中,自然m[0][j]的取值为0。当j为0时,代表当前背包的容量为0,显然无论i取何值m[i][0]的值为0。最后考虑当i为1即只有一个物品的时候,则m[1][j]的取值只与该物品的重量和当前背包的容量j有关。当w[1]<=j时,m[1][j]的值为w[1]。当w[1]>j时,m[1][j]的值为0。

【0-1背包动态规划解法的Java代码实现】

public class Test {
	
	/*
	 * 利用动态规划算法求解0-1背包问题的状态转移方程及初始值如下:
	 * 
	 * m[0][j] = 0
	 * m[i][0] = 0
	 * 
	 * if(w[1] <= j)
	 *     m[1][j] = v[1]
	 * if(w[1] > j)
	 *     m[1][j] = 0
	 *     
	 * if(w[i] > j)
	 *     m[i][j] = m[i-1][j]
	 * if(w[i] <= j)
	 *     Max{m[i-1][j],m[i-1][j-w[i]]+v[i]}
	 */
	
	public void Package(int c, int n, int w[], int v[]) {	//c:背包总容量;n:物品总个数;w:各个物品的重量;v:各个物品的价值
        
		int[][] m = new int[n+1][c+1];	//声明背包最大价值数组。m[i][j]代表:考虑将1-i的物品装入当前容量为j的背包中的最大价值
		int[] x = new int[n+1];	//声明背包状态状态数组。x[i]=1:第i个物品装入背包;x[i]=0:第i个物品不装入背包
        
        for (int i = 0; i <= n; i++) {	//初始化最大价值数组:规定当背包容量为0时,最大价值为0
			m[i][0] = 0;
		}
        for (int i = 0; i <= c; i++) {	//初始化最大价值数组:规定当物品数量为0时,最大价值为0
			m[0][i] = 0;
		}
        for (int i = 1; i <= c; i++) {	//初始化最大价值数组:规定当只有一个物品时的情况
        	if (w[1] > i) {		//如果只有一个物品,且该物品的重量比背包容量大,则无法放入背包,最大价值为0
				m[1][i] = 0;
			}else {				//如果只有一个物品,且该物品的重量比背包容量小,则可以放入背包,最大价值为该物品的价值
				m[1][i] = v[1];
			}
		}
        
        for (int i = 2; i <= n; i++) {	//考虑物品个数从2到n的过程中
			for (int j = 1; j <= c; j++) {	//考虑背包当前容量为从1到c的过程中
				if (w[i] > j) {		//考虑从第1个物品到第i个物品的过程中,如果第i个物品的重量比当前背包容量j大,则第i个物品无法装入,最大价值为考虑从第1个物品到第i-1个物品的过程的最大价值
					m[i][j] = m[i-1][j];
				}else {	//考虑从第1个物品到第i个物品的过程中,如果第i个物品的重量比当前背包容量j小,则第i个物品可以装入,最大价值为考虑从第1个物品到第i-1个物品的过程的最大价值与装入第i个物品后的最大价值的最大值
					int a = m[i-1][j];
					int b = m[i-1][j-w[i]] + v[i];
					m[i][j] = a>b?a:b;
				}
			}
		}
        
        int cc = c;
        for (int k = 1; k <= n; k++) {	//设置最后的背包装载状态数组
			if (m[k][cc] == m[k-1][cc]) {	//如果从第1个物品到第k个物品装入容量为c的背包的最大价值与从第1个物品到第k-1个物品装入容量为c的背包的最大价值相等,则代表第k个物品无法装入背包
				x[k] = 0;
			}else {		//如果从第1个物品到第k个物品装入容量为c的背包的最大价值与从第1个物品到第k-1个物品装入容量为c的背包的最大价值不相等,则代表第k个物品可以装入背包
				x[k] = 1;
				cc -= w[k];
			}
		}
        
        //打印结果信息
        System.out.println("背包的最大价值为:"+m[n][c]);
        System.out.println("最大价值背包的装载方法为:");
        for (int i = 1; i <= n; i++) {
			System.out.print(x[i]+" ");
		}
    }
	
	public static void main(String[] args) {
		Test9 test9 = new Test9();
		int[] w = {0,2,2,6,5,4};
		int[] v = {0,6,3,5,4,6};
		test9.Package(10, 5, w, v);
	}
	
}

【0-1背包动态规划解法的时间复杂度】

由上面的代码可以看出,算法的时间复杂度为O(nc)。

【YY的0-1背包问题实际场景】

0-1背包问题有没有让你想到吃鸡?除了少数物品可以部分装入你的三级包,那些不能部分装入的物品是不是就是0-1背包?有了0-1背包的算法,大家可以科学吃鸡了。

    


猜你喜欢

转载自blog.csdn.net/weixin_36378917/article/details/80843663