01背包问题 从暴力到动态规划

1.什么是背包问题?

具体题目

最基本的背包问题就是01背包问题(01 knapsack problem):一共有N件物品,第i(i从1开始)件物品的重量为w[i],价值为v[i]。在总重量不超过背包承载上限W的情况下,能够装入背包的最大价值是多少?

百度百科

背包问题(Knapsack problem)是一种组合优化的NP完全(NP-Complete,NPC)问题。问题可以描述为:给定一组物品,每种物品都有自己的重量和价格,在限定的总重量内,我们如何选择,才能使得物品的总价格最高。NPC问题是没有多项式时间复杂度的解法的,但是利用动态规划,我们可以以伪多项式时间复杂度求解背包问题。

2.背包问题有哪些类型?

 01背包:每个物品只可以使用一次,有且只有两种情况,要么放入背包,要么不放。

完全背包:商品数量是无限制的,可以向包中放入相同的多个物品。

多重背包:不同的物品,数量是不同的。(01 + 完全背包 的 结合体)。

3.如何用暴力的方式处理背包问题?

  通过上面对背包问题的理解之后,发现背包问题的核心就是:

  •        选出物品
  •        是否能放入背包 + 价值的判断 

而要满足这两个核心的业务,采用回溯算法(组合问题)加上一些额外的校验就可以解决这个问题了,而回溯算法其实就是暴力搜索。

4.回溯的思路是什么?

        假设有m个物品,先找出这m个物品所有可能的组合,然后再对这些组合判断是否能放入背包以及是否是最大价值。比如有A, B, C三个物品,所有可能的组合是A, B, C, AB, AC, BC, ABC。这其实就是数学中的组合问题了,所以首先要解决的就是如何求出m个物品的所有组合。

假设我们想在长度为n的字符串中求m个字符的组合。我们先从头扫描字符串的第一个字符。针对第一个字符,我们有两种选择:

  1. 把这个字符放到组合中去,接下来我们需要在剩下的n-1个字符中选取m-1个字符;
  2. 不把这个字符放到组合中去,接下来我们需要在剩下的n-1个字符中选择m个字符。
public class packagePro {


    public static void main(String[] args) {
        //初始化的数据
        char num[] = new char[]{'a', 'b', 'c', 'd'};
        //存储选出来的数据
        List<Character> stack = new ArrayList<>();
        List<List<Character>> res = new ArrayList<>();
        //因为要 挨个取出来,做出是否放入背包的校验
        for (int i = 1; i <= num.length; i++) {
        combine(num, 0, i, stack, res);
        }

        for (List<Character> characters : res) {
            System.out.println(characters.toString());
        }
    }

    /**
     * 从一个n个元素的数组中取出m个元素,有多少种取法
     * 从字符数组chs 中第begin个字符开始挑选number个字符加入stack中
     *
     * @param chs    字符数组
     * @param begin  第begin个字符开始
     * @param number 挑选number
     * @param stack
     */
    public static void combine(char[] chs, int begin, int number, List<Character> stack, List<List<Character>> res) {
        //不挑选,结束
        if (number == 0) {
            res.add(new ArrayList<Character>(stack));
            return;
        }
        //开始指针,走到末尾,结束
        if (begin == chs.length) {
            return;
        }
        //放入背包
        stack.add(chs[begin]);
        combine(chs, begin + 1, number - 1, stack, res);
        //不放入背包
        stack.remove(stack.size() - 1);
        combine(chs, begin + 1, number, stack, res);
    }
}

5.暴力解决的代码

背包容量:4

3样物品:A(30元,4千克)、B(20元,3千克)、C(15元,1千克)。

通过上面的组合代码实现,发现回溯的核心问题就在于是否放入背包的选择,那么我们在放入的同时计算价值,最终产生如下代码。

public class bag01 {
    //背包最大数量
    static int bagMax = 4;
    //物品价值
    static int values[] = {30, 20, 15};
    //物品重量
    static int weights[] = {4, 3, 1};
    //物品数量
    static int N = weights.length;

    public static void main(String[] args) {
        System.out.println("最终背包中的最大价值为:"+bagProblem(N - 1, bagMax));
    }

    /**
     * @param i      处理到第i件物品
     * @param bagMax 剩余的空间为
     * @return 最大价值是多少
     */
    public static int bagProblem(int i, int bagMax) {
        int maxValue = 0;

        //如果没有东西可以放入
        if (i == -1) {
            return 0;
        }

        //如果剩余空间大于所放的物品
        if (bagMax >= weights[i]) {
            //放第i件
            int value1 = bagProblem(i - 1, bagMax - weights[i]) + values[i];
            //不放第i件
            int value2 = bagProblem(i - 1, bagMax);
            maxValue = Math.max(value1, value2);
        }
        return maxValue;
    }
}

上述代码执行结果为:

动态规划-加载中......

  • 5
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值