几种背包问题(java实现)

本文详细介绍了背包问题的四种经典类型:01背包、完全背包、多重背包和混合背包,并提供了Java实现代码。针对每种类型的背包问题,文章探讨了其状态转移方程和空间优化策略,包括将二维DP数组优化为一维数组,以及如何处理物品数量有限的情况。此外,还提及了二维背包问题,即在考虑体积和重量双重限制下的动态规划解决方案。
摘要由CSDN通过智能技术生成

说说几种背包问题(java实现)


背包问题是经典的动态规划问题。分成以下几种细说一下背包问题。代码为java实现。

1. 01背包问题

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

定义一个二阶矩阵dp[N+1][V+1],
dp[i][j]表示在 只能选择前i个物品,背包容量为j的情况下,背包中物品的最大价值
这里之所以要N+1和V+1,是因为第0行表示只能选择第0个物品的时候,即没有物品的时候第0列表示背包的体积为0的时候,即不能装任何东西的时候.

已知dp[i-1][j],要求dp[i][j],则需要状态转移方程
对于dp[i][j]有两种情况:
  1.不选择当前的第i件物品/第i件物品比背包容量要大
            dp[i][j] = dp[i-1][j]
  2.选择当前的第i件物品(潜在要求第i件物品体积小于等于背包总容量),则能装入的物品最大价值为:当前物品的价值 加上 背包剩余容量在只能选前i-1件物品的情况下的最大价值。表示为:
          dp[i][j] = dp[i-1][j-v[i]] + w[i]
dp[i][j]在两种情况中选择比较大的情况作为当前的最优解;
即:
   if(j >= v[i]):
      dp[i][j] = max(dp[i-1][j], dp[i-1][j-v[i]] + w[i])
   else:
      dp[i][j] = dp[i-1][j]

以上的动态规划需要一个二维的dp空间。我们可以优化为一个一维的dp数组。此时,dp[v]就表示在v大小的背包空间下,可以放入的最大的物品价值 。此时的物品依次放入。当我们已知第i-1个物品放入后,v的最大值,那么加入第i个物品时,最大值要么是放入第i个物品,要么是原来的值。表达式:
      dp[v] = Math.max( dp[v - v[i] ) + w[i] , dp[v])
参考代码如下:

import java.util.Scanner;

public class Main{
    public static void main(String[] args) throws Exception {
        // 读入数据的代码
        Scanner reader = new Scanner(System.in);
        // 物品的数量为N
        int N = reader.nextInt();
        // 背包的容量为V
        int V = reader.nextInt();
        // 一个长度为N的数组,第i个元素表示第i个物品的体积;
        int[] v = new int[N + 1] ;
        // 一个长度为N的数组,第i个元素表示第i个物品的价值;
        int[] w = new int[N + 1] ;

        for (int i=1 ; i <= N ; i++){
        //读入是第一个数索引为1
        // 接下来有 N 行,每行有两个整数:v[i],w[i],用空格隔开,分别表示第i件物品的体积和价值
            v[i] = reader.nextInt();
            w[i] = reader.nextInt();
        }
        reader.close() ;

        // 二维dp

        int[][] dp = new int[N+1][V+1];
        dp[0][0] = 0;
        //此处i从1开始是因为上面v[i]和w[i]是从1开始存的
        for(int i = 1; i <= N; i++){
            for(int j = 0; j <= V; j++){
                if(j >= v[i]){
                    dp[i][j] = Math.max(dp[i-1][j], dp[i-1][j-v[i]] + w[i]);
                }else{
                    dp[i][j] = dp[i-1][j];
                }
            }
        }
       //压缩为一维dp数组
        int[] dp = new int[V+1];
        dp[0] = 0;
        for(int i = 1; i <= N; i++){
            for(int j = V; j >= v[i]; j--){
                dp[j] = Math.max(dp[j], dp[j-v[i]] + w[i]);
            }
        }

2.完全背包问题

有 N 种物品和一个容量是 V 的背包,每种物品都有无限件可用
第 i 种物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。输出最大价值。
可以模仿01背包,dp[i][j]记录前i个物品放入j空间的最大值。此时,由于一个物品可是使用无限次数,dp[i][j]应该从dp[i][j-v[i]]来而不是dp[i-1][j-v[i]]来。表达式为

dp[i][j]=Math.max(dp[i][j-v[i]]+w[i],dp[i-1][j])
同样,完全背包问题一样可以优化至一维dp空间。 01背包和完全背包使最常用的背包问题。很多其他背包问题可以转换为这两个问题来思考。 参考代码如下:
//数据读取和上一题01背包相同
    //模仿01背包,多维的dp。dp[i][j]记录前i个物品放入j空间的最大值。
        int[][] dp=new int[N+1][V+1];
        for(int i=1;i<=N;i++){
            for(int j=0;j<=V;j++){
                if(j>=v[i]){
                    //此时的状态方程,比较的大小
                    //注意i-1和i的状态区别
                    dp[i][j]=Math.max(dp[i][j-v[i]]+w[i],dp[i-1][j]);
                }else{
                    dp[i][j]=dp[i-1][j];
                }
               
            }
        }
        System.out.println(dp[N][V]);

    //初步优化为一维数组
    //dp[v]表示在v空间下装入的最大价值
        int[] dp=new int[V+1];
        for(int i=1;i<=N;i++){
            for(int j = V; j >= v[i]; j --){
            	//第i个物品可以使用0-k个,和不使用第i个物品的最大值比较,找到较大的一个
                //逐一找最大值,增加循环,用变量k遍历计算最大值。
                for(int k = 0; j-k* v[i] >= 0; k ++){
                    dp[j] = Math.max(dp[j] , dp[j - k * v[i]] + k * w[i]);
                }
            }
        }
        System.out.println(dp[V]);  
    
    //再优化。
        int[] dp=new int[V+1];
        for(int i=1;i<=N;i++){
        //和01背包相比在于01背包从后向前遍历,由于使用到之前的状态,从后向前时前面的状态为0,确保了一个物品只使用了一次。
        //完全背包使用从前向后遍历,前面的状态先遍历。此时后面的状态再计算时,使第i个物品重复使用。
            for(int j =  v[i]; j <= V;  j ++){
                dp[j] = Math.max(dp[j], dp[j - v[i]] + w[i]);
            }
        }
        System.out.println(dp[V]);

3 多重背包问题

有 N 种物品和一个容量是 V 的背包。
第 i 种物品最多有 si 件,每件体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
思路:
  参考完全背包问题,就是将完全背包中的数量限制改为si. (体现在for循环中)
优化:
  利用二进制,将背包数量分解,变为01背包问题。(他人处学习来的思路和代码)。
参考代码:

        //直接做法。
        int[] dp = new int[V+1];
        for(int i = 1; i <= N; i ++){
            for(int j = V; j >= v[i]; j --){
            //此处加入一个限制条件k<=s[i]即可
            //k可以从1开始,因为0就是dp[j]
                for(int k = 1; j - k * v[i] >= 0 && k <= s[i]; k ++ ){
                    dp[j] = Math.max(dp[j], dp[j - k * v[i]] + k * w[i]);
                }
            }
        }
        System.out.println(dp[V]);       
        
        //参考地址https://www.acwing.com/solution/content/7495/
        //优化的多重背包问题。        
        //将数量s分解。例如有一个体积为v,值为w,数量为7的。则分解为[v,w],[2v,2w],[4v,4w]三个物体            
        //将所有物体数量都分解。则化解为了01背包问题。       
        int maxN = 200002;
        int[] v = new int[maxN];
        int[] w = new int[maxN];
        Scanner jin = new Scanner (System.in);
        void run(){
            int n = jin.nextInt();
            int m = jin.nextInt();
            int p = 1;
            for (int i = 1; i <= n ; i++){
                int V = jin.nextInt();
                int W = jin.nextInt();
                int S = jin.nextInt();
                int k = 1;
                while (S > k){
                    v[p] = V*k;
                    w[p] = W*k;
                    S -= k;
                    k *= 2;
                    p++;
                }
                if (S > 0){
                    v[p] = V*S;
                    w[p] = W*S;
                    p ++;
                }
            }
            //到此为止,原来所有的物体分解为共p个独立的物体,每个限制使用1次,化解为01背包问题。       
            //共有p个物体,放入总体积m的背包。体积为v,价值为w
            int res = dp(p, m);
            System.out.println(res);
        }
        //同01背包问题。
        int dp(int n, int m){
            int[] f = new int[maxN];
            for (int i= 1; i <= n ; i ++){
                for (int j = m ; j>= v[i] ; j--){
                    f[j] = Math.max(f[j], f[j - v[i]] + w[i]);
                }
            }
            return f[m];
        }
        public static void main(String[] args) {new solution().run();}
        
        //利用优先队列优化多重背包问题。看到过大佬用过这个思路。
        //(待学习)

4.混合背包问题

有 N 种物品和一个容量是 V 的背包。
物品一共有三类:
第一类物品只能用1次(01背包);
第二类物品可以用无限次(完全背包);
第三类物品最多只能用 si 次(多重背包);
每种体积是 vi,价值是 wi。
si=−1 表示第 i 种物品只能用1次;
si=0 表示第 i 种物品可以用无限次;
si>0 表示第 i 种物品可以使用 si 次;
求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。

思路:
分类讨论。01背包可以看作只使用一次的多重背包问题。
参考代码:

        public static void main(String[] args){
            Scanner sc = new Scanner(System.in);
            int N = sc.nextInt(); // 物品个数
            int V = sc.nextInt(); // 背包总容量
            int[] dp = new int[V + 1];
            for(int i = 0; i < N; i++){
                int v = sc.nextInt(); // 体积
                int w = sc.nextInt(); // 价值
                int s = sc.nextInt(); // 数量
                if(s == 0){
                    // 完全背包问题
                    for(int j = v; j <= V; j++){
                        dp[j] = Math.max(dp[j], dp[j - v] + w);
                    }
                }else{
                    // 多重背包问题,01背包是多重背包的特例,可以一并处理
                    s = Math.abs(s);
                    for(int j = 1; s >= j; s -= j, j *= 2){
                        for(int k = V; k >= j * v; k--){
                            dp[k] = Math.max(dp[k], dp[k - j * v] + j * w);
                        }
                    }
                    if(s > 0){
                        for(int j = V; j >= s * v; j--){
                            dp[j] = Math.max(dp[j], dp[j - s * v] + s * w);
                        }
                    }
                }
            }
            System.out.println(dp[V]);
        }

5.二维背包问题

有 N 件物品和一个容量是 V 的背包,背包能承受的最大重量是 M。
每件物品只能用一次。体积是 vi,重量是 mi,价值是 wi。
初始的01背包问题多加入了一个限制
求解将哪些物品装入背包,可使物品总体积不超过背包容量,总重量不超过背包可承受的最大重量,且价值总和最大。
输出最大价值。
思路: 与01背包一样,二维dp扩充为三维dp

        public int two_dimension_knapsack_problem_1(int N, int V, int M, int[] v, int[] m, int[] w){
            int[][][] dp = new int[N+1][V+1][M+1];
            for(int i = 1; i <= N; i++){
                for(int j = 1; j <= V; j++){
                    for(int k = 1; k <= M; k++){
                        if(j < v[i] || k < m[i]){
                            // 客观条件限制,不能选择当前物品N
                            dp[i][j][k] = dp[i-1][j][k];
                        }else {
                        //基本相同,多个限制
                            dp[i][j][k] = Math.max(dp[i-1][j][k], dp[i-1][j-v[i]][k-m[i]] + w[i]);
                        }
                    }
                }
            }
            return dp[N][V][M];

注:许多代码与思路来源于anwin网站与相关题目题解。

  • 16
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
01背包问题是一个经典的动态规划问题,其基本思想是:用有限的容量装下最大价值的物品。回溯法是一种基于深度优先搜索的算法,可以用于解决这个问题。回溯法的基本思路是:在搜索过程中,当发现当前状态已经无法继续得到最优解时,就立即返回上一层进行剪枝,以减少搜索次数,提高效率。 在01背包问题中,回溯法的剪枝可以通过以下几个方面实现: 1. 首先,可以根据当前的物品体积和重量,计算出当前可选的物品能够达到的最大价值,如果这个价值已经小于当前最优解的价值了,就可以直接返回上一层进行剪枝。 2. 其次,在选择某个物品时,可以根据当前所选物品的体积和重量,计算出还有剩余容量能够获得的最大价值,如果这个价值加上已经选择的物品的价值,仍然小于当前最优解的价值,就可以直接返回上一层进行剪枝。 3. 最后,在搜索过程中可以记录已经搜索过的状态,避免重复搜索同样的状态,减少搜索次数。 下面是一份Java代码示例,展示了如何使用回溯法解决01背包问题实现剪枝: ``` public class Knapsack { private int maxV = Integer.MIN_VALUE; // 最大价值 private int[] w; // 物品重量 private int[] v; // 物品价值 private int n; // 物品数量 private int c; // 背包容量 public int max(int a, int b) { return a > b ? a : b; } // i表示考察到哪个物品了,cw表示当前已经装进去的物品重量和;cv表示当前已经装进去的物品价值和 public void dfs(int i, int cw, int cv) { if (cw == c || i == n) { // 装满了或者考察完了所有物品 if (cv > maxV) maxV = cv; return; } dfs(i + 1, cw, cv); // 不装第i个物品 if (cw + w[i] <= c) { // 装得下第i个物品 // 剪枝1:如果当前最大价值已经小于等于当前可选物品的最大价值,则不需要再继续搜索 if (cv + v[i] + maxV(cv + v[i], i + 1, cw + w[i]) <= maxV) return; dfs(i + 1, cw + w[i], cv + v[i]); // 装第i个物品 } } // 计算剩余物品能够获得的最大价值 public int maxV(int cv, int i, int cw) { int maxv = 0; for (int j = i; j < n; j++) { if (cw + w[j] <= c) { cw += w[j]; cv += v[j]; } else { maxv = (c - cw) * v[j] / w[j]; // 装满剩余容量可以获得的最大价值 break; } } return maxv; } public static void main(String[] args) { Knapsack k = new Knapsack(); k.w = new int[]{2, 2, 4, 6, 3}; k.v = new int[]{3, 4, 8, 9, 6}; k.n = 5; k.c = 9; k.dfs(0, 0, 0); System.out.println(k.maxV); } } ```
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值