01背包问题

1.背包问题算法

(1)问题由来:给定n个重量为w1,w2..........wn,价值为v1,v2........,vn的物品和一个承重为W的背包,求这些物品中最有价值的一个子集,并要求可以装到背包中。这里假设全部的重量和包的承重都是正整数,而物品的总重量没必要是整数。spa

(2)地推公式:为了设计一个动态规划算法,须要推导一个递推关系,用较小实例解的形式来表示背包问题的实例的解。接下来咱们来考虑一个由前i个物品定义的实例,物品的重量分别w1,w2,w3,..wi,价值分别为v1,v2,.........vi,背包承受的重量为j(j>=1,j<=w)。设F(i,j)为该实例最优解的物品总价值,也就是说可以放进承重为j的背包中的前i个物品中最有价值本身的总价值。能够将前i个物品中可以放进承重为j的背包中的子集分为两个类别:包括第i个物品的子集和不包括第i个物品的子集。设计

(3)根据上面的描述有下面的结论:3d

  根据定义,在不包括第i个物品的子集中,最优子集的价值为F(i-1,j)。code

  在包括第i个物品的子集中,最优子集是由该物品和前i-1个物品中可以放进承重为j-wi的背包的最优子集组成。这种最优子集的价值为vi+F(i-1,j-w)blog

(4)所以,在前i个物品中最优解的总价值为两个价值中的较大值。固然若是第i个物品不可以放进背包,则从前i个物品中选出的最优子集的总价值等于从前i-1个物品中选出的最优子集的总价值。这个结果又下面的递推公式。it

咱们能够比较容易的定义以下的初始条件:当j>=0的时候,F(0,j)=0;当i>=0的时候,F(i,0)=0class

咱们的目标是F(n,w),即n个给定的物品中可以放进承受重量为w的背包中的子集的最大总价值以及最优子集自己。当i,j>0的时候,为了计算第i行第j列的单元格F(i,j),咱们拿前一行同一列的单元格与vi加上前一行左边wi列的单元格的和做比较,计算出二者的较大者。test

(5)实例:考虑下列数据给出的实例效率

下图给出了由动态规划公示计算的动态规划表

最大的总价值为F(4,5)=37能够经过上表回溯过程来求得最优子集元素,由于F(4,5)=37,F(3,5)=32,物品4填满背包余下5-2=3个单位承重量的一个最优子集都包括在最优解中。

(6)记忆化

 动态规划所涉及问题的解知足一个交叠子问题来表示递推关系。直接使用自顶向下的这样一个地推关系求解致使算法要不止一次的求解公共子问题,所以算法的效率比较低(通常来讲是指数级的),另外一方面经典动态规划是自底向上工做的,它用全部较小的子问题来填充表格,可是每一个子问题只解一次,这种方法没法使人满意的一面是,在求解给定问题时,有些较小子问题的解经常不是必须的。因此咱们使用记忆法,该方法使用自顶向下的方式对给定问题进行求解,可是还须要维护一个相似自底向上动态规划算法使用的表格。算法思想以下:

 

 2.实例:

 

package com.nowcoder.dp;

import org.junit.Test;

public class Knapsack {
    public static void main(String[] args){

    }

    /**
     *
     * @param val 实例最优解的最大值
     * @param wight 物品的重量
     * @param w 背包容量
     * @return
     */

    public static int knapsack(int[] val,int[] wight,int w){
        int n = val.length;   //物品的总数量
        int[][] v= new int[n+1][w+1];   //建立背包矩阵
        //对于第0行全部的列来讲,他们没有选择物品的权利不能选择物品,因此无论背包容量多少,总价值都是0
        for(int col = 0 ; col<= w ; col++){
          v[0][col] = 0;
        }
        //对于第0列全部的行来讲,背包容量为0,不能再向背包中听任何物品
        for(int row = 0 ; row <= n ;row++){
            v[row][0]=0;
        }
        /**
         * 接下来填充记录表
         */
        for(int i = 1 ; i <=n;i++){//先一行一行的填充
            for(int j =1 ; j <= w ; j++){  //再在每一行中按列来填充
                if(wight[i-1]<=w){//若是当前物品的重量小于当前背包的重量
                    v[i][j] = Math.max(val[i-1]+v[i-1][w-wight[i-1]],v[i-1][w]);
                }else {//若是当前物品的重量大于当前背包中的重量
                    v[i][j] = v[i-1][j];

                }
            }
        }
        return v[n][w];
    }
    @Test
    public void test(){
        int[] val = {10, 40, 30, 50};
        int[] wight={5, 4, 6, 3};
        int w = 10 ;
        System.out.println(knapsack(val,wight,w));
    }
}