问题描述
给定n种物品和一个背包。物品i的重量是Wi,其价值为Vi背包的容量为C。问如何选择装入背包中的物品,使得装入背包中物品的总价值最大?
01背包问题的目标是使背包中物品的价值最大。约束条件有两个:一是物品的重量不能超出背包的容量。二是物品只能整体放入背包,不能部分放入。
问题分析
(1)最优子结构
反证法
设(y1,y2,……,yn)是所给01背包问题的一个最优解,则(y2,y3,……,yn)是子问题的一个最优解。子问题满足以下条件:一是物品的总价值是物品y2,y3,……,yn的总价值和;二是物品的总重量小于等于C-(w1y1),否则。设(Z1,Z2,……,Zn)是上述子问题的一个最优解,则(y1,z1,Z2……,yn)是比(y1,y2,……,yn)更优的解。(矛盾)
故01背包问题具有最优子结构性质。构造01背包问题的递归结构
设所给0-1背包的子问题的最优值为m(i,j),即背包容量为j,可选择物品为i,i+1……n时的最优值,通过递归关系求得最优值表m。
对01背包问题的队规关系分两种情况。
装入物品n的情况下可分为两种情况,若物品n的重量大于背包的容量时,无法装入物品n,若物品n的重量小于等于背包的容量时,装入物品n
二是装入的可选物品m(i,j),物品无法装入时,即物品i的重量大于背包剩余容量的情况下;能够装入物品,即物品i的重量小于等于背包剩余容量的情况。
java代码
package com.xiang;
import java.util.Scanner;
/**
* 使用二维数组非递归的方法求解0/1背包问题
*/
public class ZeroOnePack {
// N表示物体的个数,V表示背包的载重
int N,V;
//用于存储每个物体的重量,下标从1开始
private int[] weight;
//存储每个物体的收益,下标从1开始
private int[] value;
//二维数组,用来保存每种状态下的最大收益
private int[][] F;
/**
* 使用非递归方式,求解F[0 .. N][0 .. V],即for循环从下至上求解
*/
public void ZeroOnePackNonRecursive() {
//对二维数组F进行初始化
for(int j = 0; j <= V; j++) {
F[0][j] = 0;
}
//注意边界问题,i是从1开始的,j是从0开始的
//因为F[i - 1][j]中i要减1
for(int i = 1; i <= N; i++) {
for(int j = 0; j <= V; j++) {
//如果容量为j的背包放得下第i个物体
if(j >= weight[i]) {
F[i][j] = Math.max(F[i - 1][j - weight[i]] + value[i], F[i - 1][j]);
}else {
//放不下,只能选择不放第i个物体
F[i][j] = F[i - 1][j];
}
}
}
//打印所有结果,我们要求的是F[N][V]
for(int i = 0; i <= N; i++) {
for(int j = 0; j <= V; j++) {
System.out.print(F[i][j] + " ");
}
System.out.println();
}
}
/**
* 求解F[n][m]这个最优值具体选取哪几样物品能获得最大价值,但只会输出一种情况
* @param n 表示前n个物体,n <= N
* @param v 表示背包的容量,v <= V
*/
public void printResult(int n, int v) {
boolean[] isAdd = new boolean[n + 1];
for(int i = n; i >= 1; i--) {
if(F[i][v] == F[i-1][v])
isAdd[i] = false;
else {
isAdd[i] = true;
v -= weight[i];
}
}
for(int i = 1; i <= n; i++) {
System.out.print(isAdd[i] + " ");
}
System.out.println();
}
/**
* 输入格式:
5 10
2 2 6 5 4
6 3 5 4 6
* result:15
* 第一行是物体个数、背包总空间;
* 第二行是每个物体的空间;
* 第三行是每个物体的收益。
*/
public void init() {
Scanner sc = new Scanner(System.in);
N = sc.nextInt();
V = sc.nextInt();
//下标从1开始,表示第1个物品
weight = new int[N + 1];
value = new int[N + 1];
F= new int[N + 1][V + 1];//注意是 N + 1,因为需要一个初始状态F[0][0],表示前0个物品放进空间为0的背包的最大收益
for(int i = 1; i <= N; i++) {
weight[i] = sc.nextInt();
}
for(int i = 1; i <= N; i++) {
value[i] = sc.nextInt();
}
}
public static void main(String[] args) {
ZeroOnePack zop = new ZeroOnePack();
zop.init();
zop.ZeroOnePackNonRecursive();
zop.printResult(zop.N,zop.V);
}
}