动态规划算法解决背包问题(Java实现)

文章收藏的好句子:你在书本上花的任何时间,都会在某一个时刻给你回报。

目录

1、动态规划算法的概述

2、背包问题

3、动态规划算法解决背包问题

     3、1 不可重复装入商品

     3、2 思路分析

1、动态规划算法的概述

(1)动态规划算法的思想是:将大问题分为小问题进行解决,使得一步步获得最优解的处理算法。

(2)动态规范算法与分治算法很类似,思想都是以待解决问题先分解成 n 个子问题,先求解子问题,然后从子问题中得到原问题的解。

(3)动态规划求解的问题与分治算法不同的点在于,经过分解出来的子问题不是相互独立的,下一个子问题的求解是建立在上一个子问题解决的求解基础上的,依次递进获取最终解。

(4)动态规划可以通过填表的方式一步步推进,最终得到最优解。

2、背包问题

这里就有一个背包问题了,有一个容量为4磅的背包,需要装入如下表1的物品,怎样才能使装入包中的商品最值钱且不超过4磅重?

b67a4f1e53118a6d81f12fc432316a8b.png

3、动态规划算法解决背包问题

上面的背包问题,放入商品的总质量不能超过4磅且要实现背包装入商品价值的最大化,这里假设放入的商品是不能重复的, 可用一个二维表格来表示。

3、1 不可重复装入商品

我先画一个表格2,然后会对这个表格2进行详细的说明,如下所示;

5b417a7165e108668904cf26d16313aa.png

说明:列表示背包能装多大的重量,横表示物品的类型和商品数量,行和列交叉而出现的数据表示背包能装的物品总和的最大价值。

好,我们现在对表2的数据分析一下,第一行的数据全部为0,这个可以理解吧?不管背包能放多大的重量,只要不放入物品,那就是0咯;第一列的数据全部为0,是因为背包能装0磅;我们看第二行第二列的数据到第二行第五列的数据,首先第二行能放的物品只能是鞋子且不能重复对不对?那不管背包(装的重量大于等于1磅)能装多少磅的物品,都是只能放1磅的鞋子对不对?那自然就是1500了。

我们看第三行第二列到第三行第五列的数据,第三行能放入的物品是鞋子、音响且同一个物品不能放超过1个,第三行的第二列到第三行的第四列只能放1磅的鞋子,放不下音响(音响4磅重),所以就是1500了,而第三行的第五列刚好能放4磅的音响,而且放一个音响比放一双鞋子更值钱对不对?

第四行可以放的商品有鞋子、音响和电脑,好,现在看第四行的第二列到第四行的第三列,由于第二列和第三列是只能装1磅和2磅对不对?那就只能放一双鞋子了,所以就是1500了;第四行的第四列能装3磅,那就是可以放一双鞋子或者一台电脑,由于电脑比鞋子更值钱对不对,所以放电脑更好点,所以就是2000了;看第四行的第五列,背包可以装4磅,那这里面就有较好一点的两种方案选择了,一种是放一双鞋子和一台电脑,另一种是只放一台音响,一看放一双鞋子和一台电脑的方案更值钱对不对,所以是1500+2000就是3500了。

从表2可以小结一下

(1)横行表示背包容量从0到指定容量的各种情况,这是第一步的分,将大容量的背包先转化为小容量背包,算出子问题的最优解,然后一步步加大容量,算出最终问题的最优解。

(2)纵行表示商品信息,且第一横行为空值,作为初始数据的对比值;纵行是第二步的分,先将一个商品放入背包中,算出最优解,逐渐增加商品类型和商品数量,算出最终最优解。

(3)最终表格的最右下角的格子,即为数据的最优解;看表2最右下角的数据3500,是不是最优解。

3、2 思路分析

(1)利用动态规划来解决,假设给定的 n 个物品,设 v[i]、 w[i] 分 别为第 i 个商品的价值和重量,C 为背包的容量。

(2)每次遍历到的第 i 个商品,根据 w[i] 和 v[i] 来确定是否需要将该商品放入背包中;这句话说的是什么意思呢?我举个例子,你们就理解了,看表2的第四行的第四列的2000这个数据,首先第四列背包最大容量是3磅对不对?第四行能放的商品有鞋子、音响和电脑对不对?但是音响比背包的容量更大,所以就只能放鞋子和电脑,鞋子和电话的重量和超过3磅对不对,所以又只能从鞋子和电脑里面挑选一个放进去,由于电脑比鞋子更值钱对不对?所以放电脑价值更大对不对?所以是根据 w[i] 和 v[i] 来确定是否需要将该商品放入背包中。

(3)再令 v[i][j] 表示在前 i 个商品中能够装入容量为 j 的背包中的最大价值;这句话又是什么意思啊?我再举个例子,看表2的第三行第五列的3000,这时候 i 就是2,j 就是4,v[i][j] 就是 v[2][4],也就是 v[2][4] 为3000;第二行只能装的商品是鞋子对不对?第三行能装入的商品包含第二行装入的商品,也就是说第三行能选择装入的商品是鞋子和音响;如果鞋子和音响同时放入背包(背包容量为4磅,j=4)肯定是装不下的对不对?所以从鞋子和音响里面选最值钱的放入背包中,所以就是 v[i][j] 表示在前 i 个商品中能够装入容量为 j 的背包中的最大价值。

现在我们从思路分析和表2中能够总结出以下几条公式;

(1)v[i][0] = v[0][j] = 0,其中 i 表示第几行,j 表示第几列;看表2,第一列的数据是不是0?第一行的数据是不是也是0?

(2)当 w[i] > j 时,有 v[i][j] = v[i-1][j],w 表示第 i+1 行商品的重量 ;举例:看表2中的第三行的第二列,i = 2,w[i] = 4,j = 1,v[2][1] = v[2-1][1]  = 1500 。

(3)当j >= w[i] 时,有 v[i][j]=max{v[i-1][j],v[i-1][j-w[i]]+v[i]} ,v[i] 表示第 i+1 行商品的价格;举例:看表2,看第四行的第五列数据,j = 4,i = 3,w[3] = 3,v[3] = 2000,那么 v[3][4] = max{v[3-1][4] , v[3-1][4-w[3]]+v[3]} = max{3000 , 3500},所以 v[3][4] = 3500 。

好,我们现在用代码实现一把;

(1)新建一个 Test 类:

package com.xiaoer.demo;


/**
 * 动态规划: 背包问题
 **/
public class Test {


    public static void main(String[] args) {
      Test test = new Test();
      
        // 商品名字数组
        String[] nameArr = {"鞋子", "音响", "电脑"};
        
        // 商品重量数组
        int[] weightArr = {1/*鞋子*/, 4/*音响*/, 3/*电脑*/};
        
        // 商品价格数组
        int[] priceArr = {1500/*鞋子*/, 3000/*音响*/, 2000/*电脑*/};
        
        // 背包容量
        int packageCapacity = 4;
        
        // 把不可重复的商品装入背包
        test.backpackWithoutRepeat(nameArr, weightArr, priceArr, packageCapacity);
    }




    /**
     * 装入背包
     * @param nameArr 商品名字数组
     * @param w 商品重量数组
     * @param priceArr 商品价格数组
     * @param packageCapacity 背包容量
     */
    private void backpackWithoutRepeat(String[] nameArr, int[] w, int[] priceArr, int packageCapacity) {
        /**
         * 声明一个能装入 0、1、2、3磅......的背包的二维价格表;举例:就好比 v数组是表2的数据
         */
        int[][] v = new int[nameArr.length + 1][packageCapacity + 1];
        
        // 构建可能装入背包的二维数组
        // 值为0时说明不会装进背包, 值为1说明可能装入背包
        int[][] contentArr = new int[nameArr.length + 1][packageCapacity + 1];
        
        /**
         * 为什么i一开始是1不是0?看表2的数据,是不是第一行全是0啊
         */
        for (int i = 1; i < v.length; i++) {
            
          /**
           * 为什么j一开始是1不是0?看表2的数据,是不是第一列全是0啊
           */
            for (int j = 1; j < v[i].length; j++) {
                
              /**
               * 文章中当 w[i] > j 时,就有 v[i][j] = v[i-1][j];
               * 因为我们程序i是从1开始的,因此原来公式中的w[i]修改成w[i-1];
               * 当前商品 > 背包容量, 取同列上一行数据
               */
                if (w[i - 1] > j) {
                    v[i][j] = v[i - 1][j];
                } else {
                    /**
                     *  当前商品 <= 背包容量, 对两部分内容进行比较;
                     *  第一部分, 该列上一行数据
                     */
                    int onePart = v[i - 1][j];
                    
                    /**
                     * 还记得文章中写的 当j >= w[i] 时,有 v[i][j]=max{v[i-1][j],v[i-1][j-w[i]]+v[i]} 这个公式成立吗?
                     * priceArr[i - 1]: 当前商品价格;
                     * w[i - 1]: 当前商品重量;
                     * j - w[i - 1]: 去掉当前商品, 背包剩余容量;
                     * 不可重复: v[i - 1][j - w[i - 1]]: 在上一行, 取剩余重量下的价格最优解;
                     */
                    int otherPart = priceArr[i - 1] + v[i - 1][j - w[i - 1]];
                    
                    /**
                     *  取最大值为当前位置的最优解
                     */
                    v[i][j] = Math.max(onePart, otherPart);
                    
                    /**
                     *  如果最优解包含当前商品, 则表示当前商品已经被使用, 进行记录
                     */
                    if (otherPart == v[i][j]) {
                        contentArr[i][j] = 1;
                    }
                }
            }
        }


        // 不能重复的场景中
        // 如果该位置的标志位为1, 说明该商品参与了最终的背包添加
        // 如果该位置的标志位为0, 即使该位置的价格为最大价格, 也是从其他位置引用的价格
        // 因为不能重复, 所以每行只取一个数据参与最终计算, 并只判断在最大位置该商品是否参与
        // 该最大位置会随着已经遍历出其他元素而对应不断减小, 直到为0


        // 二维数组最后一个元素必然是最大值, 但是需要知道该最大值是自身计算的 还是比较后引用其他的
        int totalPrice = 0;
        // 最大行下标数, 即商品数
        int maxLine = contentArr.length - 1;
        // 最大列下标数, 即重量
        int maxColumn = contentArr[0].length - 1;
        for (;maxLine > 0 && maxColumn > 0;) {
            // 等于1表示在该位置该商品参与了计算
            if (contentArr[maxLine][maxColumn] == 1) {
                // 遍历后, 对重量减少, 下一次从剩余重量中取参与商品
                maxColumn -= w[maxLine - 1];
                totalPrice += priceArr[maxLine - 1];
                System.out.println(nameArr[maxLine - 1] + "加入了背包");
            }
            // 因为不能重复
            // 所以如果该商品参与了背包容量, 则肯定剩余的最大位置处参与,
            // 否则跟该数据无关, 直接跳过
            maxLine--;
        }
        System.out.println("背包可容纳的最大价值: " + totalPrice);
    }


}

程序运行结果如下所示;

1acecf106358b219df81626f2867a326.png

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值