01背包+完全背包+多重背包+组合背包(java版)

典型的背包问题分为四类:

组合背包:有的物品只可以取一次(01背包),有的物品可以取无限次(完全背包),有的物品可以取的次数有一个上限(多重背包)。

模板:

https://leetcode-cn.com/problems/target-sum/solution/yi-pian-wen-zhang-chi-tou-bei-bao-wen-ti-wa5r/

https://leetcode-cn.com/problems/word-break/solution/yi-tao-kuang-jia-jie-jue-bei-bao-wen-ti-kchg9/

参考自背包九讲:

https://blog.csdn.net/yandaoqiusheng/article/details/84782655

总结一下:

背包分类的模板:

01背包:外循环nums,内循环target,target倒序且target>weight[i]

完全背包:外循环nums,内循环target,target正序且target>weight[i]

多重背包:外循环nums,内循环target,内内循环k,target倒序且target>weight[i](我不明白为什么上面的连接里面外层是target,内层是nums,有懂的同学可不可以评论区解释一下)

组合背包:需要三重循环:外循环nums,内部两层循环,根据题目的要求转化为以上三种背包类型的模板

问题分类的模板:

1、最值问题:dp[i] = max/min(dp[i], dp[i-nums]+nums)或dp[i] = max/min(dp[i], dp[i-num]+1);

2、组合问题:dp[i]=dp[i]+dp[i-num];

3、存在问题(bool):dp[i]=dp[i]||dp[i-num];

01背包:

import java.util.*;
/**
 * @author wyl
 */
public class Main {
    public static void main(String[] args) {
        //物品个数
        int numbers = 4;
        //背包容量
        int capacity = 11;
        //个体容量
        int[] weight = {0,6,2,3,5};
        //个体价值
        int[] values = {0,23,12,10,1};
        //当前背包容量 j的物品最佳组合对应的价值
        int[] v = new int[capacity + 1];
        for (int i = 1; i < numbers + 1; i++) {
            for (int j = capacity; j >= weight[i]; j--) {
                v[j] = Math.max(v[j], v[j - weight[i]] + values[i]);
            }
        }
        System.out.println(v[capacity]);
    }
}

完全背包:

import java.util.*;
/**
 * @author wyl
 */
public class Main {
    public static void main(String[] args) {
        //物品个数
        int numbers = 4;
        //背包容量
        int capacity = 11;
        //个体容量
        int[] weight = {0,6,2,3,5};
        //个体价值
        int[] values = {0,23,12,10,1};
        //当前背包容量 j的物品最佳组合对应的价值
        int[] v = new int[capacity + 1];
        for (int i = 1; i < numbers + 1; i++) {
            for (int j = weight[i]; j <= capacity; j++) {
                v[j] = Math.max(v[j], v[j - weight[i]] + values[i]);
            }
        }
        System.out.println(v[capacity]);
    }
}

多重背包(类似01背包):

/**
 * @author wyl
 */
public class Main {
    public static void main(String[] args) {
        //物品个数
        int numbers = 4;
        //背包容量
        int capacity = 11;
        //个体容量
        int[] weight = {0, 6, 2, 3, 5};
        //个体价值
        int[] values = {0, 23, 12, 10, 1};
        //第i种物品最多有p[i]件可用
        int[] p = {0, 1, 2, 3, 4};
        //当前背包容量 j的物品最佳组合对应的价值
        int[] v = new int[capacity + 1];
        //这是未优化的版本:
        for (int i = 1; i < numbers + 1; i++) {
            for (int j = capacity; j >= weight[i]; j--) {
                for (int k = 0; k <= p[i] && k * weight[i] <= j; k++) {
                    v[j] = Math.max(v[j], v[j - weight[i] * k] + values[i] * k);
                }
            }
        }
        System.out.println(v[capacity]);
    }
}

组合背包:

/**
 * @author wyl
 */
public class Main {
    public static void main(String[] args) {
        //物品个数
        int numbers = 4;
        //背包容量
        int capacity = 11;
        //个体容量
        int[] weight = {0, 6, 2, 3, 5};
        //个体价值
        int[] values = {0, 23, 12, 10, 1};
        //第i种物品最多有p[i]件可用
        int[] p = {0, 1, 2, 3, 4};
        //当前背包容量 j的物品最佳组合对应的价值
        int[] v = new int[capacity + 1];

        for (int i = 1; i <= numbers; i++) {
            //代表每个物品都有无穷个,所以是完全背包
            if (p[i] == 0) {
                for (int j = weight[i]; j <= capacity; j++) {
                    v[j] = Math.max(v[j], v[j - weight[i]] + values[i]);
                }
            } else {
                //代表每个物品为有限个,所以是01背包或者多重背包
                //注意这里的for循环,k在外面,与多重背包不同,多重背包的k在里面
                //把问题从“这个物品能取多少次”转化为了“有多少个这样的物品”
                //多以转移方程不用乘以k了。
                for (int k = 1; k <= p[i]; k++) {
                    for (int j = capacity; j >= weight[i]; j--) {
                        v[j] = Math.max(v[j], v[j - weight[i]] + values[i]);
                    }
                }
            }
        }
        System.out.println(v[capacity]);
    }
}

--------------------------------------------------------------------我是分割线----------------------------------------------------------------------------

下面的不是很全。

01背包:

题解见:https://blog.csdn.net/Rex_WUST/article/details/89336939

二维简化版:

import java.util.*;
/**
 * @author wyl
 */
public class Main {
    public static void main(String[] args) {
        //物品个数
        int numbers = 4;
        //背包容量
        int capacity = 11;
        //个体容量
        int[] weight = {0,6,2,3,5};
        //个体价值
        int[] values = {0,23,12,10,1};
        //当前背包容量 j,前i个物品最佳组合对应的价值
        int[][] v = new int[numbers + 1][capacity + 1];
        for (int i = 1; i < numbers + 1; i++) {
            for (int j = 1; j < capacity + 1; j++) {
                if (j < weight[i]) {
                    v[i][j] = v[i - 1][j];
                } else {
                    //假设不装最后一个体积为5的物品,则前三个物品:6,2,3装入容量为j=11的背包中,价值为23+12+10=45
                    //装了最后一个体积为5的物品,则剩余容量为j-w[5]=11-5=6,装剩余前三个物品:6,2,3,价值最大为23+1=24
                    //所以,装了还不如不装。
                    v[i][j] = Math.max(v[i - 1][j], v[i - 1][j - weight[i]] + values[i]);
                }
            }
        }
        System.out.println(v[numbers][capacity]);
    }
}

二维完整版:

package test;

import java.util.*;

public class Main {

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        while (sc.hasNext()) {

            /* 1.读取数据 */

            int number = sc.nextInt(); // 物品的数量

            // 注意:我们声明数组的长度为"n+1",并另score[0]和time[0]等于0。
            // 从而使得 数组的下标,对应于题目的序号。即score[1]对应于第一题的分数,time[1]对应于第一题的时间
            int[] weight = new int[number + 1]; // {0,2,3,4,5} 每个物品对应的重量
            int[] value = new int[number + 1]; // {0,3,4,5,6} 每个物品对应的价值

            weight[0] = 0;
            for (int i = 1; i < number + 1; i++) {
                weight[i] = sc.nextInt();
            }

            value[0] = 0;
            for (int i = 1; i < number + 1; i++) {
                value[i] = sc.nextInt();
            }

            int capacity = sc.nextInt(); // 背包容量

            /* 2.求解01背包问题 */

            int[][] v = new int[number + 1][capacity + 1];// 声明动态规划表.其中v[i][j]对应于:当前有i个物品可选,并且当前背包的容量为j时,我们能得到的最大价值

            // 填动态规划表。当前有i个物品可选,并且当前背包的容量为j。
            for (int i = 0; i < number + 1; i++) {
                for (int j = 0; j < capacity + 1; j++) {
                    if (i == 0) {
                        v[i][j] = 0; // 边界情况:若只有0道题目可以选做,那只能得到0分。所以令V(0,j)=0
                    } else if (j == 0) {
                        v[i][j] = 0; // 边界情况:若只有0分钟的考试时间,那也只能得0分。所以令V(i,0)=0
                    } else {
                        if (j < weight[i]) {
                            v[i][j] = v[i - 1][j];// 包的容量比当前该物品体积小,装不下,此时的价值与前i-1个的价值是一样的,即V(i,j)=V(i-1,j);
                        } else {
                            v[i][j] = Math.max(v[i - 1][j], v[i - 1][j - weight[i]] + value[i]);// 还有足够的容量可以装当前该物品,但装了当前物品也不一定达到当前最优价值,所以在装与不装之间选择最优的一个,即V(i,j)=max{V(i-1,j),V(i-1,j-w(i))+v(i)}。
                        }
                    }
                }
            }

            System.out.println();
            System.out.println("动态规划表如下:");
            for (int i = 0; i < number + 1; i++) {
                for (int j = 0; j < capacity + 1; j++) {
                    System.out.print(v[i][j] + "\t");
                }
                System.out.println();
            }
            System.out.println("背包内最大的物品价值总和为:" + v[number][capacity]);// 有number个物品可选,且背包的容量为capacity的情况下,能装入背包的最大价值

            /* 3.价值最大时,包内装入了哪些物品? */

            int[] item = new int[number + 1];// 下标i对应的物品若被选中,设置值为1
            Arrays.fill(item, 0);// 将数组item的所有元素初始化为0

            // 从最优解,倒推回去找
            int j = capacity;
            for (int i = number; i > 0; i--) {
                if (v[i][j] > v[i - 1][j]) {// 在最优解中,v[i][j]>v[i-1][j]说明选择了第i个商品
                    item[i] = 1;
                    j = j - weight[i];
                }
            }

            System.out.print("包内物品的编号为:");
            for (int i = 0; i < number + 1; i++) {
                if (item[i] == 1) {
                    System.out.print(i + " ");
                }
            }
            System.out.println("----------------------------");

        }

    }

}

优化空间复杂度版本:(这个是最终版,好多题都是这个模板)

关于初始状态:

如果背包并非必须被装满,那么任何容量的背包都有一个合法解“什么都不装”,这个解的价值为0,所以初始时状态的值也就全部为0了。这个小技巧完全可以推广到其它类型的背包问题,后面也就不再对进行状态转移之前的初始化进行讲解。

来源:https://blog.csdn.net/yandaoqiusheng/article/details/84782655?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522162149549616780265413777%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=162149549616780265413777&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~top_positive~default-1-84782655.first_rank_v2_pc_rank_v29&utm_term=%E8%83%8C%E5%8C%85%E4%B9%9D%E8%AE%B2&spm=1018.2226.3001.4187

import java.util.*;
/**
 * @author wyl
 */
public class Main {
    public static void main(String[] args) {
        //物品个数
        int numbers = 4;
        //背包容量
        int capacity = 11;
        //个体容量
        int[] weight = {0,6,2,3,5};
        //个体价值
        int[] values = {0,23,12,10,1};
        //当前背包容量 j的物品最佳组合对应的价值
        int[] v = new int[capacity + 1];
        for (int i = 1; i < numbers + 1; i++) {
            for (int j = capacity; j >= weight[i]; j--) {
                v[j] = Math.max(v[j], v[j - weight[i]] + values[i]);
            }
        }
        System.out.println(v[capacity]);
    }
}

一个常数优化

前面的代码中有f o r ( j = V . . . w [ i ] ) for(j=V...w[i])for(j=V...w[i]),还可以将这个循环的下限进行改进。
由于只需要最后f [ j ] f[j]f[j]的值,倒推前一个物品,其实只要知道f [ j − w [ n ] ] f[j-w[n]]f[j−w[n]]即可。以此类推,对以第j jj个背包,其实只需要知道到f [ j − s u m ( w [ j . . . n ] ) ] f[j-sum({w[j...n]})]f[j−sum(w[j...n])]即可,即代码可以改成

import java.util.*;
/**
 * @author wyl
 */
public class Main {
    public static void main(String[] args) {
        //物品个数
        int numbers = 4;
        //背包容量
        int capacity = 11;
        //个体容量
        int[] weight = {0,6,2,3,5};
        //个体价值
        int[] values = {0,23,12,10,1};
        //当前背包容量 j的物品最佳组合对应的价值
        int[] v = new int[capacity + 1];
        for (int i = 1; i < numbers + 1; i++) {
            int numSum = 0;
            for (int k = i + 1; k < numbers + 1; k++) {
                numSum = weight[k] + numSum;
            }
            int bound = Math.max(capacity - numSum, weight[i]);
            for (int j = capacity; j >= bound; j--) {
                v[j] = Math.max(v[j], v[j - weight[i]] + values[i]);
            }
        }
        System.out.println(v[capacity]);
    }
}

完全背包:

import java.util.*;
/**
 * @author wyl
 */
public class Main {
    public static void main(String[] args) {
        //物品个数
        int numbers = 4;
        //背包容量
        int capacity = 11;
        //个体容量
        int[] weight = {0,6,2,3,5};
        //个体价值
        int[] values = {0,23,12,10,1};
        //当前背包容量 j,前i个物品最佳组合对应的价值
        int[][] v = new int[numbers + 1][capacity + 1];
        for (int i = 1; i < numbers + 1; i++) {
            for (int j = 1; j < capacity + 1; j++) {
                if (j < weight[i]) {
                    v[i][j] = v[i - 1][j];
                } else {
                    //完全背包就是变成了v[i][j-weight[i]]+values[i],
                    //在第i个物品上面判断是不是需要再往上面添加物品i
                    v[i][j] = Math.max(v[i - 1][j], v[i][j - weight[i]] + values[i]);
                }
            }
        }
        System.out.println(v[numbers][capacity]);
    }
}

优化空间版本:

import java.util.*;
/**
 * @author wyl
 */
public class Main {
    public static void main(String[] args) {
        //物品个数
        int numbers = 4;
        //背包容量
        int capacity = 11;
        //个体容量
        int[] weight = {0,6,2,3,5};
        //个体价值
        int[] values = {0,23,12,10,1};
        //当前背包容量 j的物品最佳组合对应的价值
        int[] v = new int[capacity + 1];
        for (int i = 1; i < numbers + 1; i++) {
            for (int j = weight[i]; j <= capacity; j++) {
                v[j] = Math.max(v[j], v[j - weight[i]] + values[i]);
            }
        }
        System.out.println(v[capacity]);
    }
}

多重背包:

题目
有N种物品和一个容量为V的背包。第i种物品最多有p[i]件可用,每件费用是w[i],价值是v[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。

转换为01背包:

/**
 * @author wyl
 */
public class Main {
    public static void main(String[] args) {
        //物品个数
        int numbers = 4;
        //背包容量
        int capacity = 11;
        //个体容量
        int[] weight = {0, 6, 2, 3, 5};
        //个体价值
        int[] values = {0, 23, 12, 10, 1};
        //第i种物品最多有p[i]件可用
        int[] p = {0, 1, 2, 3, 4};
        //当前背包容量 j的物品最佳组合对应的价值
        int[] v = new int[capacity + 1];
        //这是未优化的版本:
        for (int i = 1; i < numbers + 1; i++) {
            for (int j = capacity; j >= weight[i]; j--) {
                for (int k = 0; k <= p[i] && k * weight[i] <= j; k++) {
                    v[j] = Math.max(v[j], v[j - weight[i] * k] + values[i] * k);
                }
            }
        }
//        这是优化之后的版本(回头看看背包九讲...):
//        for (int i = 1; i < numbers + 1; i++) {
//            int num = Math.min(p[i], capacity / weight[i]);
//            for (int k = 1; num > 0; k <<= 1) {
//                if (k > num) {
//                    k = num;
//                }
//                num = num - k;
//                for (int j = capacity; j >= weight[i] * k; j--) {
//                    v[j] = Math.max(v[j], v[j - weight[i] * k] + values[i] * k);
//                }
//            }
//        }
        System.out.println(v[capacity]);
    }
}

组合背包:

问题
如果将前面三个背包混合起来,也就是说,有的物品只可以取一次(01背包),有的物品可以取无限次(完全背包),有的物品可以取的次数有一个上限(多重背包),应该怎么求解呢?

01背包与完全背包的混合:
考虑到在01背包和完全背包中给出的伪代码只有一处不同,故如果只有两类物品:一类物品只能取一次,另一类物品可以取无限次,那么只需在对每个物品应用转移方程时,根据物品的类别选用顺序或逆序的循环即可,复杂度是O(VN)。

再加上多重背包:
如果再加上有的物品最多可以取有限次,那么原则上也可以给出O(VN)的解法:遇到多重背包类型的物品用单调队列解即可。但如果不考虑超过NOIP范围的算法的话,用多重背包中将每个这类物品分成O(log(p[i]))个01背包的物品的方法也已经很优了。当然,更清晰的写法是调用我们前面给出的三个相关过程。代码:

/**
 * @author wyl
 */
public class Main {
    public static void main(String[] args) {
        //物品个数
        int numbers = 4;
        //背包容量
        int capacity = 11;
        //个体容量
        int[] weight = {0, 6, 2, 3, 5};
        //个体价值
        int[] values = {0, 23, 12, 10, 1};
        //第i种物品最多有p[i]件可用
        int[] p = {0, 1, 2, 3, 4};
        //当前背包容量 j的物品最佳组合对应的价值
        int[] v = new int[capacity + 1];

        for (int i = 1; i <= numbers; i++) {
            //代表每个物品都有无穷个,所以是完全背包
            if (p[i] == 0) {
                for (int j = weight[i]; j <= capacity; j++) {
                    v[j] = Math.max(v[j], v[j - weight[i]] + values[i]);
                }
            } else {
                //代表每个物品为有限个,所以是01背包或者多重背包
                //注意这里的for循环,k在外面,与多重背包不同,多重背包的k在里面
                //把问题从“这个物品能取多少次”转化为了“有多少个这样的物品”
                //多以转移方程不用乘以k了。
                for (int k = 1; k <= p[i]; k++) {
                    for (int j = capacity; j >= weight[i]; j--) {
                        v[j] = Math.max(v[j], v[j - weight[i]] + values[i]);
                    }
                }
            }
        }
        System.out.println(v[capacity]);
    }
}

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值