算法基础四:动态规划---0-1背包问题
算法基础四:动态规划---0-1背包问题
一、算法描述与分析
1、问题的理解与描述
问题理解
问题描述
2、解题思路
①思路
②状态转移方程
f(k,w):当背包容量为w,现有k件物品可以偷所能偷到的最大价值。
③表格(图示)
解释:
- 第一行和第一列为0,因为当背包容量为0的时候,不论还有几件物品可以偷,那么价值都是0,偷不到。如果剩下0件物品可以偷,那么价值也是0。相当于初始化。
- 比如f(1,2),意思就是背包容量还剩下2,现在还有一件物品可以偷,这个物品的重量为2,价值为3。或者带入转移方程也可以看出来,偷与不偷的情况。
二、算法的伪代码描述
1.算法伪代码
KNAPSACK(v, w, C)
n <- length[v]
for j <- 0 to C
do f[0,j] <-0 #j控制容量
for i<-1 to n
do f[i,0] <-0
for j<-1 to C
do f[i,j] <- f[i-1,j]
if wi <= j
then if vi + f[i-1,j-wi] > f[i-1,j
then f[i,j] <- vi + f[i-1,j-wi
return f
2、构造一个最优解
利用KNAPSACK返回的矩阵m,可以构造出如下最优解:
BUILD-SOLUTION(f,w,C)
n <- length[w]
j <- C
for i<- n to 1
do if f[i,j] = f[i-1,j]
then x[i] <- 0
else
x[i] <- 1
j <- j-w[i]#如果拿了的话,容量减少
return x
三、代码实现
1、算法代码
public class Knapsack {
public static int[][] knapsack(int[] w,int[] v,int c){
int i,j,n = w.length;
int [][] f = new int[n+1][c+1];
for (i=1;i<n+1;i++)
f[i][0] = 0;
for (j=0;j<c+1;j++)
f[0][j] = 0;
for (i=1;i<=n;i++)
for (j=1;j<=c;j++){
f[i][j] = f[i-1][j];//默认其太重放不下。或者是不拿当前这个物品的情况。
if (w[i-1]<=j)//此种情况就是如果当前物品能放下
if (v[i-1]+f[i-1][j-w[i-1]]>f[i-1][j])//并且拿了这个物品的总价值要大于不拿这个物品
f[i][j] = v[i-1]+f[i-1][j-w[i-1]];
}
return f;
}
public static int[] buildSolution(int[][] f,int[] w,int c){
int i,j=c,n=w.length;
int[] x = new int[n];
for (i=n;i>=1;i--)
if (f[i][j] == f[i-1][j])//说明当前这个物品,可能是没拿,也有可能是放不下。
x[i-1] = 0;//那么就说明这个物品没拿
else {//否则就说拿了,需要减去这个重量,重新的去寻找下一个拿了的物品。
x[i-1] = 1;
j -= w[i-1];
}
return x;
}
}
2、测试代码
public class Test {
public static void main(String[] args) {
int w[] = {2,3,4,5},v[]={3,4,5,7};
int[][] f;
int[] x;
f = Knapsack.knapsack(w,v,9);
x = Knapsack.buildSolution(f,w,9);
for (int i = 0; i < 4; i++) {
System.out.print(x[i]+" ");
}
System.out.println();
}
}
四、思考求解动态规划
1、组成部分一:确定状态
①最后一步:
在本题中,最后一步,也就是从最后一个物品出发,最后一个物品,拿还是不拿。不拿的价值是多少,拿了的价值是多少。
②子问题:
前一个物品拿完之后,当前这个物品拿不拿,拿了之后价值和没拿之后的价值。
2、组成部分二:转移方程
列出转移方程
3、初始条件和边界情况
- 数组要开多大?
- 如果重量为0,值该怎么办。
- 如果没有物品可以拿,值该怎么办。
- 循环要多少次
4、计算顺序
- 由于我们是从左到右的,一直到右下角,在计算当前的时候,会参考二维数组的左面,左斜上方的格子里 的值,自然我们的计算顺序是从左到右,从上到下。