背包问题

Posted toria

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了背包问题相关的知识,希望对你有一定的参考价值。

  • 0-1背包问题  :每个物品只有1件
  • 完全背包问题:每个物品有无数件
  • 多重背包问题:每个物品有不超过多少件的限制
  • 混合背包问题:物品有的是1件,有的无数件,有的不超过多少件

1、0-1背包问题

题目描述:

  有N件物品和一个容量是bagV的背包,每件物品只能使用一次。第 i件物品的体积是 v[i],价值是 w[i]
  求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。输出最大价值。

思路:

对于每一个物品,有两种结果:能装下或者不能装下。

  • 如果不能装下,这时的最大价值和前i-1个物品的最大价值是一样的;
  • 如果能装下,装了不一定大于当前相同体积的最优价值,所以要对装该商品与不装该商品得到的最大价值进行比较,取最大的那个。

f[i][j]表示:背包容量为j时,前i个物品所能达到的最大价值。0<=j<=V
第i个商品体积为vi,价值为wi,则状态转移方程:

  • j<vi, f[i][j] = f[i-1][j]     //背包装不下此物品,最大价值不变,还是为前i-1的最大价值
  • j>=vi,f[i][j] = maxf[i-1][j],f[i-1][j-vi]+wi    // 背包装得下,最大价值取装与不装该物品时同样达到该体积的最大价值
技术图片
    /**
     * 利用二维数组
     * @param N  N个物品
     * @param bagV  背包体积为bagV
     * @param v  物品体积(v[i]表示第i个物品体积,v[0]=0)
     * @param w  物品价值(w[i]表示第i个物品价值,w[0]=0)
     */
    public static int bag0_1(int N, int bagV, int[] v, int[] w) 
        
        //f[i][j]表示背包容量为j时前i个商品的最大价值
        int[][] f = new int[N+1][bagV+1];
        for(int i = 1; i <= N; i++) 
            for(int j = 0; j <= bagV; j++) 
                if(j < v[i])
                    f[i][j] = f[i-1][j];
                else
                    f[i][j] = Math.max(f[i-1][j], f[i-1][j-v[i]]+w[i]);
            
        
        return f[N][bagV];
    
View Code

用一维数组的话,设f[j]表示背包容量为j时的最大价值,状态转移方程:f[j] = max[f[j],f[j-vi]+wi

    /**
     * 利用一维数组实现
     * @param N  N个物品
     * @param bagV  背包体积为bagV
     * @param v  物品体积(v[i]表示第i个物品体积,v[0]=0)
     * @param w  物品价值(w[i]表示第i个物品价值,w[0]=0)
     */
    public static int bag0_1(int N, int bagV, int[] v, int[] w) 
        //f[j]表示背包体积为j时最大价值
        int[] f = new int[bagV + 1];
        for(int i = 1; i <= N; i++) 
            for(int j = bagV; j >= v[i]; j--)
                f[j] = Math.max(f[j], f[j-v[i]]+w[i]);
        
        return f[bagV];
    

2、完全背包问题

 题目描述:

  有 N 种物品和一个容量是 bag的背包,每种物品都有无限件可用第 i 种物品的体积是 vi,价值是 wi

  求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。输出最大价值。

思路:

设f[i][j]表示背包容量为j时,前i个物品所能达到的最大价值。0<=j<=bagV
第i个商品体积为vi,价值为wi,则状态转移方程:

  • j<vi,f[i][j] = f[i-1][j]     //背包装不下此物品,最大价值不变,还是为前i-1的最大价值
  • j>=vi,f[i][j] = maxf[i-1][j],f[i-1][j-k*vi]+k*wi   //背包装得下,最大价值取装与不装该物品时同样达到该体积的最大价值,与0-1不同的是,可以装k个。
技术图片
    /**
     * 利用二维数组实现
     * @param N  N个物品
     * @param bagV  背包体积为bagV
     * @param v  物品体积(v[i]表示第i个物品体积,v[0]=0)
     * @param w  物品价值(w[i]表示第i个物品价值,w[0]=0)
     */
    public static int competeBag(int N, int bagV, int[] v, int[] w)     
        //f[i][j]表示背包容量为j时前i个商品的最大价值
        int[][] f = new int[N+1][bagV+1];
        for(int i = 1; i <= N; i++) 
            for(int j = 0; j <= bagV; j++) 
                if(j < v[i])
                    f[i][j] = f[i-1][j];
                else
                    for(int k = 1; k*v[i] <= j; k++)
                        f[i][j] = Math.max(f[i-1][j], f[i-1][j-k*v[i]]+k*w[i]);
                
            
        
        return f[N][bagV];
    
View Code

  利用一维数组的话,设f[j]表示背包容量为j时的最大价值,状态转移方程:f[j] = max[f[j],f[j-k*vi]+k*wi

技术图片
/**
     * 利用一维数组实现
     * @param N  N个物品
     * @param bagV  背包体积为bagV
     * @param v  物品体积(v[i]表示第i个物品体积,v[0]=0)
     * @param w  物品价值(w[i]表示第i个物品价值,w[0]=0)
     */
    public static int competeBag(int N, int bagV, int[] v, int[] w)     
        //f[j]表示背包容量为j时商品的最大价值
        int[] f = new int[bagV+1];
        for(int i = 1; i <= N; i++) 
            for(int j = 0; j <= bagV; j++) 
                for(int k = 1; k * v[i] <= j; k++) 
                    f[j] = Math.max(f[j], f[j-k*v[i]]+k*w[i]);
                
            
        
        return f[bagV];
    
View Code

优化代码:与0-1背包不同的是第二层循环j从小到大顺序遍历(0-1背包是从大到小逆序遍历)

    /**
     * 利用一维数组实现
     * @param N  N个物品
     * @param bagV  背包体积为bagV
     * @param v  物品体积(v[i]表示第i个物品体积,v[0]=0)
     * @param w  物品价值(w[i]表示第i个物品价值,w[0]=0)
     */
    public static int competeBag(int N, int bagV, int[] v, int[] w)     
        //f[j]表示背包容量为j时商品的最大价值
        int[] f = new int[bagV+1];
        for(int i = 1; i <= N; i++) 
            for(int j = v[i]; j <= bagV; j++)
                f[j] = Math.max(f[j], f[j-v[i]]+w[i]);
        
        return f[bagV];
    

3、多重背包问题

题目描述:

  有 种物品和一个容量是 bagV 的背包。第 i 种物品最多有 s,每件体积是 vi,价值是 wi。

  求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。输出最大价值。

思路:

  和完全背包类似,不同的是第二层循环j时多了一个对物品个数的限制。

    /**
     * 利用一维数组实现
     * @param N  N个物品
     * @param bagV  背包体积为bagV
     * @param v  物品体积(v[i]表示第i个物品体积,v[0]=0)
     * @param w  物品价值(w[i]表示第i个物品价值,w[0]=0)
     * @param s  s[i]表示第i个物品最多有多少个 ,s[0]=0
     */
    public static int multipleBag(int N, int bagV, int[] v, int[] w, int[] s)     
        //f[j]表示背包容量为j时商品的最大价值
        int[] f = new int[bagV+1];
        for(int i = 1; i <= N; i++) 
            for(int j = 0; j <= bagV; j++) 
                for(int k = 1; k <= s[i] && k * v[i] <= j; k++) 
                    f[j] = Math.max(f[j], f[j-k*v[i]]+k*w[i]);
                
            
        
        return f[bagV];
    

利用二进制优化,转化为0-1背包问题:

  一个数a,我们可以按照二进制来分解为 a=1+2+4+8……+2^n+剩下的数,我们把a拆成这么多项,可以证明,这么多项可以组合出1~a的每一个数。

  不管最优策略选择几件第i种物品,总可以表示成若干件物品的和。利用二进制拆分将a拆成若干数字的和,假设拆成M个数字,则这样把原问题转化为物品数量为M的0-1背包问题

//先定义一个类来存放新商品
class Goods 
    int v; //体积
    int w; //价值
    public Goods(int v, int w) 
        this.v = v;
        this.w = w;
    

-----------------------------------------------

    /**
     * 二进制优化,转为0-1背包问题来实现
     * @param N  N个物品
     * @param bagV  背包体积为bagV
     * @param v  物品体积(v[i]表示第i个物品体积,v[0]=0)
     * @param w  物品价值(w[i]表示第i个物品价值,w[0]=0)
     * @param s  s[i]表示第i个物品最多有多少个 ,s[0]=0
     */
    public static int multipleBag(int N, int bagV, int[] v, int[] w, int[] s)     
        //存放新商品的体积、价值
        ArrayList<Goods> list = new ArrayList<Goods>();
        //s[i]拆为一些数的和,重新存放商品,二进制转换为0-1背包问题
        for(int i = 1; i <= N; i++) 
            int ss = s[i];
            for(int k = 1; k <= ss; k *= 2) 
                ss -= k;
                list.add(new Goods(k*v[i], k*w[i]));
            
            //剩下的数
            if(ss > 0)
                list.add(new Goods(ss*v[i], ss*w[i]));
        
        
        //按照0-1背包问题求解
        int[] f = new int[bagV+1];
        for(Goods good : list) 
            for(int j = bagV; j >= good.v; j--) 
                f[j] = Math.max(f[j], f[j-good.v]+good.w);
            
        
        return f[bagV];
        
    

4、混合背包问题

有 N种物品和一个容量是 bagV的背包。物品一共有三类

  • 第一类物品只能用1次(01背包);
  • 第二类物品可以用无限次(完全背包);
  • 第三类物品最多只能用 si 次(多重背包);

每种体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。输出最大价值。

 

这里我们给出输入输出格式:

输入格式:
  第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。
  接下来有 N行,每行三个整数 vi,wi,si,用空格隔开,分别表示第 i种物品的体积、价值和数量。
    si=−1 表示第 i种物品只能用1次;
    si=0 表示第 i种物品可以用无限次;
    si>0 表示第 i种物品可以使用 si 次;
输出格式:
  输出一个整数,表示最大价值。

输入样例:
4 5
1 2 -1
2 4 1
3 4 0
4 5 2
输出样例:8

思路:

  将多重背包转换为0-1背包进行处理,所以最后只需要处理两种背包:0-1背包与完全背包。

//定义一个Goods类
class Goods 
    int v; //体积
    int w; //价值
    int s; //物品类型:-1、0、>0
    public Goods(int v, int w, int s) 
        this.v = v;
        this.w = w;
        this.s = s;
        


public class Main 

    public static void main(String[] args) 
        Scanner sc = new Scanner(System.in);
        int N = sc.nextInt(); //N个物品
        int bagV = sc.nextInt(); //背包容积为bagV
        //存储物品的体积和价值
        int[] v = new int[N+1]; //体积
        int[] w = new int[N+1]; //价值
        int[] s = new int[N+1]; //物品类型:-1--只有1件、0--有无数件、>0--有这些件
        for(int i = 1; i <= N; i++) 
            v[i] = sc.nextInt();
            w[i] = sc.nextInt();
            s[i] = sc.nextInt();
        
        System.out.println(mixtureBag(N, bagV, v, w, s));
    
    
    /*
     * s[i]=-1: 0-1背包
     * s[i]=0 :完全背包
     * s[i]>0 :多重背包
     * 多重背包可以转换为0-1背包进行处理
     */
    public static int mixtureBag(int N, int bagV, int[] v, int[] w, int[] s) 
        //存放商品的 体积、价值、类型
        ArrayList<Goods> list = new ArrayList<Goods2>();
        
        for(int i = 1; i <= N; i++)
            if(s[i] == -1 || s[i] == 0)
                list.add(new Goods(v[i], w[i], s[i]));
            else 
                //多重背包二进制优化转为0-1背包问题
                int ss = s[i];
                for(int k = 1; k <= s[i]; k *= 2) 
                    ss -= k;
                    list.add(new Goods(k*v[i], k*w[i], -1));
                
                if(ss > 0)
                    list.add(new Goods(ss*v[i], ss*w[i], -1));
            
        
        
        int[] f = new int[bagV+1];
        for(Goods good : list) 
            //0-1背包
            if(good.s == -1)
                for(int j = bagV; j >= good.v; j--)
                    f[j] = Math.max(f[j], f[j-good.v]+good.w);
            
            //完全背包
            else
                for(int j = good.v; j <= bagV; j++)
                    f[j] = Math.max(f[j], f[j-good.v]+good.w);
            
        
        return f[bagV];
    
    

 

 

 

参考:dd大牛的《背包九讲》

代码练习:https://www.acwing.com/problem/

以上是关于背包问题的主要内容,如果未能解决你的问题,请参考以下文章

背包问题

动态规划第五篇:01背包问题和完全背包问题

动态规划第五篇:01背包问题和完全背包问题

动态规划/背包问题背包问题第一阶段最终章:混合背包问题

动态规划/背包问题背包问题第一阶段最终章:混合背包问题

背包问题(01背包)