网上介绍动态规划和背包问题的文章多不胜数,这里就不再去粘贴复制哪些概念性的东西了,直接切入主题。
先将0/1背包问题的分析简单概括一下,我从正反两个方向阐述:
场景:现在有若干个物品,每个物品有自己的重量和价值,背包有一定的容量,在背包能容纳的下的情况下随便装入物品(物品不可分割),要求获取的价值要最大化。(本文请忽略背包用"容量"来装"重量"~~,还有为了通俗,也尽量少用数学符号)
正向分析、如果前i个物品在背包容量为j时,我们获取到了最优解,达到了最大价值(定义此最大价值为V),如果再多一个物品(给这个物品一个标识:x,重量为wx),背包容量不变仍为j,问题变成了,前i+1个物品在背包容量为j时的最大价值,那么为多少呢?
对于x物品来说,它的命运只有两个:放/不放。
我们首先需要考虑一个最基本的问题,这个容量j能否放的下x物品?不然说再多都是浪费表情,如果放不下,那么很简单,我们直接不理它,此时即使多了x物品,最大价值也=V。
如果放得下,我们再来考虑放还是不放(我们要价值最大化,所以取两者间的最大值)
1>如果不放,那么很简单,我们直接不理他,此时得到一个价值V1(显然V1==V)
2>如果放,那我们就需要重新分配这个背包,此时的最大价值为:x的价值(要放x,肯定得算上它的价值噻) + 前i个物品(这些物品我们没有动,该咋的咋的)在容量为 j-wx(放入x后,背包的可分配容量只剩j-wx)时的最大价值,我们定义为V2。所以,此时最大价值=V1 > V2 ? V1 : V2
反向分析、上面的正向分析,直接假设了前i个物品在背包容量为j时的最优解V,那么我们来考虑V=啥?
我们认为i物品的重量为wi,其实和上面的分析一样,V=第i个物品的价值 + 前i-1个物品在容量为j-wi时的最大价值,等等,这个等式成立的条件是什么?
没错,前提是我们为了获取最优解,是将i物品放入背包了的,而i物品放入背包的条件又是什么?当然是容量能够容纳i,并且在能够容纳i的情况下,放i比不放i,最终的价值更大。
下面列出代码(java,随便写的测试代码,看个意思就行了,仅供参考~~)
正向:嵌套for循环方式,此方式为从最小子问题(0个物品,背包容量为0)开始到最终解(i个物品,背包容量为j)。使用二维数组保存子问题最优解
反向:递归
package com.loren;
/**
* Created by loren on 2017/12/16.
*/
public class Test01BeiBao {
//物品数量
static int num = 4;
//每个物品的价值数组
static int value[] = {10, 40, 30, 50};
//每个物品的重量数组和value一一对应
static int weight[] = {5, 4, 6, 3};
//背包容量
static int W = 10;
public static void main(String[] args) {
System.out.println("for循环-结果:" + getResult(num, value, weight, W));
System.out.println("递归-结果:" + getResultDG(num - 1, W));
}
/**
* 获取结果(递归版)
*
* @param i 物品下标从0开始,表示前i个物品
* @param j
* @return
*/
public static int getResultDG(int i, int j) {
if (j == 0) {
//背包容量为0时,最大价值=0
return 0;
}
if (i == 0) {
//递归到前0个物品时,表示背包容量为j,只有第一个物体可选时的最大价值
//如果j容量能够容纳下该物品,返回该物品的价值,否则返回0
if (j >= weight[0]) {
return value[0];
} else {
return 0;
}
}
//前i个物品在容量为j时的最大价值=i-1个物品在容量为j-weight[i]的最大价值+value[i]
//这个结论的前提是为了达到最大价值,是将第i个物品放入背包了的,否则最大价值=i-1个物品在容量为j时的价值
//而第i个物品是否放入背包的判断条件是:背包容量允许放入此物品且放入后的价值比不放的价值高
//判断是否需要放入此物品
if (weight[i] <= j) {
int a = getResultDG(i - 1, j - weight[i]) + value[i];
int b = getResultDG(i - 1, j);
return a > b ? a : b;
} else {
return getResultDG(i - 1, j);
}
}
/**
* @param value
* @param weight
* @param W 背包重量
* @return
*/
public static int getResult(int num, int[] value, int[] weight, int W) {
//初始化二维数组,用于保存i个物品在容量为j是的最大价值
int[][] tab = new int[num + 1][W + 1];//整个二维数组需要包含0个物品在容量为j时和i个物品在容量为0时的最大价值,需要
//初始化数组,0个物品在容量为j时和i个物品在容量为0时的最大价值均为0
//之所以初始化tab[0][i]和tab[i][0],是为了在计算到第一个物品时方便,便于理解
for (int i = 0; i <= W; i++) {
tab[0][i] = 0;
}
for (int i = 0; i <= num; i++) {
tab[i][0] = 0;
}
for (int i = 1; i <= num; i++) {
//外层循环每个物品
for (int j = 1; j <= W; j++) {
//内层循环当前背包容量
if (weight[i - 1] <= j) {
//如果当前物品能够放进背包,有两种情况:放或者不放,当前最大价值只需要取两种情况之间的最大值即可
//放:最大价值=当前物品的价值+(i-1)个物品在(当前容量-当前容量)时的最大价值
int v1 = value[i - 1] + tab[i - 1][j - weight[i - 1]];
//不放:最大价值=(i-1)个物品在当前容量时的最大价值
int v2 = tab[i - 1][j];
//取最大值
tab[i][j] = v1 >= v2 ? v1 : v2;
} else {
//如果当前物品不能放进背包,那么价值就为i-1个物品(除去当前物品)在j容量时的价值
tab[i][j] = tab[i - 1][j];
}
}
}
//输出整个二维数组
System.out.print(" ");
for (int i = 0; i <= W; i++) {
System.out.print("容量:" + i + " ");
}
System.out.println();
for (int i = 0; i < tab.length; i++) {
System.out.print("物品:" + i + "value=" + (i > 0 ? value[i - 1] : 0) + ",weight=" + (i > 0 ? weight[i - 1] : 0) + (i == 0 ? " " : " "));
for (int j = 0; j < tab[0].length; j++) {
System.out.print(tab[i][j] + (tab[i][j] >= 10 ? " " : " "));
}
System.out.println();
}
System.out.println("----------------------");
return tab[value.length][W];//返回所有物品在容量为W时的最大价值
}
}