0-1背包详解与算法实现(上)
仅以此记录我的学习过程
01背包问题描述
概念与数学模型
给定n种物品与1背包,物品 i 的重量为
w
i
w_{i}
wi,其价值为
v
i
v_{i}
vi,背包的容量为C,问如何选择物品,使得装入背包后总价值最大?
数学模型:
m
a
x
∑
i
=
1
n
v
i
x
i
max\sum_{i=1}^nv_ix_i
max∑i=1nvixi
{ ∑ i = 1 n v i x i ≤ C x i ∈ { 0 , 1 } , 1 ≤ i ≤ n \left\{ \begin{aligned} \sum_{i=1}^nv_ix_i\le C \\ x_i \in \{0,1\},1\le i \le n \\ \end{aligned} \right. ⎩⎪⎪⎨⎪⎪⎧i=1∑nvixi≤Cxi∈{0,1},1≤i≤n
例子与求解
给定4物品,每个物品的重量与价值用(v,w)来表示。背包容量为8。
编号分别为1,2,3,4的物品介绍如下:
物编 | 1 | 2 | 3 | 4 |
---|---|---|---|---|
物重 | 2 | 3 | 4 | 5 |
物价 | 3 | 4 | 5 | 8 |
-
法一:
记f(k,w):当背包容量为w,剩余k件物品可以选择放入,所能达到最大价值。
我们从第 4 个物品开始选择取或不取,标记 1 为取,0为不取,2为无法取(背包容量不足),第一层为编号4 的物品,第二层为3号物品,依次向下。
例如:取四号物品,那么从初始 f(4,8)到 f (3 , 3) +8 ;此时取得总价值为8。
那么可以清晰看到,图中最大值为12,取了4号物品,无法取3号,取了2号,无法取1号。所以,取物品为2与4 。价值最大为12。 -
法二:
v | w | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
3 | 2 | 0 | 0 | 3 | 3 | 3 | 3 | 3 | 3 | 3 |
表格表示:当背包容量分别为0,1,2,3,…,8时,只有物品 1 时,能得到的最大价值为 3 。
v | w | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
3 | 2 | 0 | 0 | 3 | 3 | 3 | 3 | 3 | 3 | 3 |
4 | 3 | 0 | 0 | 3 | 4 | 4 | 7 | 7 | 7 | 7 |
同理:当背包容量分别为0,1,2,3,…,8时,只有物品1,2时,所能达到最大价值为 7 。
那么,得到最终结果为12。
动态规划法Java实现
01背包具有最优子结构的性质 ,
m(i,j)表示最优值,其中背包容量为 j , 可选择物品为 i ,i+1,… ,n,可以建立如下递归
m
(
i
,
j
)
=
{
max
{
m
(
i
+
1
,
j
)
,
m
(
i
+
1
,
j
−
w
i
)
+
v
i
}
j
≥
w
i
m
(
i
+
1
,
j
)
0
≤
j
<
w
i
m(i,j)= \left\{ \begin{aligned} \max \{m(i+1,j),m(i+1,j-w_i)+v_i\}& & j\geq w_i \\ m(i+1,j) & & 0\le j<w_i\\ \end{aligned} \right.
m(i,j)={max{m(i+1,j),m(i+1,j−wi)+vi}m(i+1,j)j≥wi0≤j<wi
m
(
n
,
j
)
=
{
v
n
j
>
w
n
0
0
≤
j
<
w
n
m(n,j)= \left\{ \begin{aligned} v_n& & j> w_n \\0 & & 0\le j<w_n\\ \end{aligned} \right.
m(n,j)={vn0j>wn0≤j<wn
对于第一个式子,如果背包剩余容量 j 大于 i 物品重量,则进行判断,否则不取物品 i ,最优值同上一个值.
第二个式子表示,要么取第n个物品,要么不取.(此时第n个物品为开始选择的第一个)
原理同法二:取值从最后一个物品开始.
import java.util.Scanner;
public class dynamic_01 {
//用来记录输入的重量与价值对应到数组
int i = 0;
static int[] val = new int[4];
static int[] wei = new int[4];
//存储最优值的二维数组
private int[][] m;
//背包容量
private static int totalWeight;
//二维数组用来记录最优值,
//计算最优值函数并返回二维数组,主函数中输出
private int[][] knapsack(int[] v,int[] w, int c){
m = new int[c+1][c+1];
int n = v.length -1; //先取最后一个物品开始计算,根据例子,n = 3;
int jMax = Math.min(w[n]-1,c); //比较最后一个物品的重量与背包容量比较(判断是否取该物品) jMax = min(w[3]-1=4,8) = 4
//此时可以将背包看作离散的点,1,2,...,n。
// 如果是物重大于背包,则放不下,即m[n]这个一维数组全置零。
// 否则,m[n]初始化时,背包容量小于物重时置零,大于置v[n]
//例子:m[3][0]=0,m[3][1]=0,...,m[3][4]=0;m[3][5]=8,...,m[3][8]=8;
for(int j = 0; j<= jMax ; j++)
m[n][j] = 0;
for (int j = w[n]; j <= c; j++) {
m[n][j] = v[n];
}
//此时m[3][]这个一位数组已经填好,接下来以它作对照开始递归
for (int j = n-1; j >= 0 ; j--) {
jMax = Math.min(w[j]-1,c);
//现在对于m[2][]这个一维数组,此时背包已经选择过是否取物品3,
// 所以当背包空间小于物品2的重量时,使得m[2][k]的最优值等于m[3][k]即可,k从0到w[2],
//当背包空间大于物品2的重量时,则比较,是同样选择m[3][k]这个最优值还是选择m[3][k-w[3]]+v[2] (这个式子表示把本物品装入后再加上剩余容量的价值);
for (int k = 0; k <= jMax ; k++) {
m[j][k] = m[j+1][k];
}
for (int k = w[j]; k <= c; k++) {
m[j][k] = Math.max(m[j+1][k],m[j+1][k-w[j]]+v[j]);
//如对于该例题,上面知道了m[3][6]=8,此时m[j][k]=m[2][6]=max(m[3][6],m[3][6-4]+5)=max(8,5)=8
}
}
return m;
}
public static void main(String[] args) {
dynamic_01 dr = new dynamic_01();
dr.sys_in();
int[][] s = dr.knapsack(val,wei,totalWeight);
int maxV = s[0][0];
int fi=0,fj=0;
for (int i = 0; i < wei.length; i++) {
for (int j = 0; j < dynamic_01.totalWeight+1; j++) {
if(s[i][j]>maxV) {
maxV = s[i][j];
fi=i;
fj=j;
}
System.out.print(s[i][j]+"--");
}
}
System.out.println();
System.out.println("最大价值为:"+ maxV);
}
//记录输入的值
private void sys_in(){
Scanner sc = new Scanner(System.in);
System.out.println("物品/价值输入为0则结束");
System.out.println("请输入背包容量");
int room = sc.nextInt();
totalWeight = room;
while(true){
System.out.println("请输入物品重量");
int weight = sc.nextInt();
if(weight == 0) break;
wei[i] = weight;
System.out.println("请输入物品价值");
int value = sc.nextInt();
if(value == 0) break;
val[i] = value;
i += 1;
}
}
}
对于例题,结果输出如下: