小白的算法之路(一)----01背包

前言

之前思考过这个问题,细想无果,老办法:上网求助
在浏览了众多博客之后,有了一些想法,在此进行记录。仅作为学习笔记
如有错误之处,劳驾您指出来,您的指点是我的荣幸。

注:主要参考自这是一篇博客这也是一片博客

解法

题目:
假设山洞里共有e,d,c,b,a这5件宝物(不是5种宝物),它们的重量分别是4,5,6,2,2,它们的价值分别是6,4,5,3,6,现在给你个承重为10的背包, 怎么装背包,可以才能带走最多的财富?

思路

在此用Vi表示第 i 个物品的价值;Wi表示第 i 个物品的重量;
F(i,j)表示在当前背包容量为 j 的时候,从 i 个物品序列中进行选择能达到的最大价值
例如第3个物品指的是c,以此类推。

在添加一个新物品进背包的时候,有两种选择:

  • 新添加的物品的重量大于当前背包容量,自然而然的我们无法将其加入背包中;
  • 新添加的物品的重量小于当前背包容量,所以我们可以选择将新的物品加入背包或者选择不将其加入背包中。

注:下列所举的例子跟前文提及的题目无关。

第一种选择

对于第一种情况,新添加的物品重量大于当前背包的容量,所以不需要改变原来背包中的物品序列(例如原本的物品序列为:a,b),即:遇到新的物品后,其价值仍然是V(a)+V(b)我们可以将其转换成:在遇到第i个物品时有:F(i,j)=F(i-1,j);(ps:这是个赋值语句)
例如:
在背包容量为5的情况下,背包里有2个物品:a,b;当遇到第3个物品c后,发现其重量大于当前背包的重量(Wc>5),所以有F(3,5)=F(2,5)
所以可以得出:当Wi> j;有F(i,j)=F(i-1,j)

第二种选择

对于第二种情况,新添加的物品重量小于当前背包的容量,我们面临选择:要不要将其换入背包中
换与不换:
例如在背包容量为9的情况下,背包里面此时有3个物品:a,b,c;此时遇到第4个物品d,若要换入,其实换个想法就是我们默认了:在容量9的情况下,从a,b,c,d四个物品中选择具有最高价值的物品序列中一定有一个d,但换入以后是否真的是最高价值,还需要和Va+Vb+Vc进行比较(其实就是F(i-1,j)
即:当Wi<= j时,有:F(i,j)=max{ F(i-1,j),F(i-1,j-Wi)+Vi }
F(i-1,j)就是不换的情况下得到的最高价值
F(i-1,j-Wi)+Vi 指的是如果我们换,默认其是达到最高价值的最优序列中的一员,放入d之后,背包的容量为 j-Wi ,但此时又出现了一个新的问题A:原有背包中的a,b,c是否都不需要进行改动呢?这点我们不知道,只能通过计算得知。
但对于问题A,我们是不是能理解为:在背包容量为 j-Wi 的情况下,从a,b,c三个物品中怎么选择才能达到最高价值(不用考虑是否能够全部装下a,b,c),那么我们怎么得到背包容量为 j-Wi 的最优序列呢?待会揭晓…

动态规划

下图的表格:最上面一行代表“当前”背包容量( j ),第一列代表物品序列( i )
例如:e(i=1),d(i=2),c(i=3),b(i=4),a(i=5)
注:下面会出现例如:c-1,实际上就是3-1=2,也就是指d

12345678910
e0006666666
d000666661010
c000666661011
b033669991011
a066991212151515

该怎么理解这个表格呢??
表格是从e行1列单元格,先列后行进行填写。
第b行0列代表的是:当背包容量为1的时候,从e,d,c,b四种物品中怎么挑选,才能达到最高价值?
第d行7列代表的是:当背包容量为7的时候,从e,d两种物品中怎么挑选,才能达到最高价值?



基于这个表格,
例如当我们计算第c行8列的时候:
Wc<j;F(c,j)=max{ F(c-1,j),F(c-1,j-Wc)+Vc }
亦即:
6<8;F(3,8)= max{ F(2,8),F(2,8-6)+5 }
=》F(3,8)=max{ F(2,8),F(2,2)+5 }
=》F(3,8)= max{ 6,0+5 }
=》F(3,8)= 6
而在我们计算F(3,8)之前,我们就已经计算了F(2,2),所以直接引用即可。这就是问题A的解决办法。

注:
动态规划方法仔细安排求解顺序,对每个子问题只求解一次,并将结果保存下来。如果随后再次出现此子问题的解,只需查找保存的结果,而不必重新计算。因此,动态规划算法是付出额外的内存空间来节省计算时间,是典型的时空权衡的例子。 -----《算法导论》

题目代码实现


/**
 * 使用动态规划解决01背包问题
 */
public class finalDemo01 {

    /**
     * 假设山洞里共有e d c b a这5件宝物(不是5种宝物),它们的重量分别是4,5,6,2,2,它们的价值分别是6,4,5,3,6
     * 现在给你个承重为10的背包, 怎么装背包,可以才能带走最多的财富
     *
     * @param args
     */

    public static void main(String[] args) {
        //定义矩阵
        //假设有5个物品e d c b a背包容量为10
        //其重量分别为:4,5,6,2,2
        //价值为:6,4,5,3,6
        //定义价值矩阵,横坐标为背包的容量
        int[][] valueMartix = new int[6][11];
        //为了方便后面的取值,将没有意义的行列置0,这样取值就不必考虑行列为0的情况
        for (int a = 0; a < 11; a++) {
            valueMartix[0][a] = 0;
        }
        for (int a1 = 0; a1 < 5; a1++) {
            valueMartix[a1][0] = 0;
        }


        //定义物品的重量数组
        int[] weight = {0, 4, 5, 6, 2, 2};
        //定义价值数组
        int[] value = {0, 6, 4, 5, 3, 6};

        int capacity = 10;
        //开始填写价值矩阵
        int i = 1;
        int j = 1;


        while (j <= capacity) {
            for (; i < weight.length; i++) {
                if (j < weight[i]) {
                    valueMartix[i][j] = valueMartix[i - 1][j];
                } else {
                    valueMartix[i][j] = max(valueMartix[i - 1][j], valueMartix[i - 1][j - weight[i]] + value[i]);
                }
            }
            i = 1;
            j++;
        }
        //输出最佳方案对应的最大价值
        System.out.println(valueMartix[5][10]);
    }

    public static int max(int v1, int v2) {
        if (v1 >= v2) {
            return v1;
        } else {
            return v2;
        }
    }
}

总代码实现

再进一步的拓展:


/**
 * 最终版本,删减了一些不必要的注释
 * 使用动态规划方法解决01背包问题
 * 自底向上
 */
public class finalDemo02 {


    public static void main(String[] args) {

        //定义货物的数量
        int goodsNumber;
        //定义背包的容量
        int capacity;

        //输入货物的数量和背包的总容量
        Scanner sc = new Scanner(System.in);
        System.out.print("请输入货物的数量");
        goodsNumber = sc.nextInt();
        System.out.print("请输入背包的总容量");
        capacity = sc.nextInt();

        //定义矩阵
        //定义价值矩阵,横坐标为背包的容量
        int[][] valueMartix = new int[goodsNumber + 1][capacity + 1];
        //为了方便后面的取值,将没有意义的行列置0,这样取值就不必考虑行列为0的情况
        for (int a = 0; a < capacity; a++) {
            valueMartix[0][a] = 0;
        }
        for (int a1 = 0; a1 < goodsNumber; a1++) {
            valueMartix[a1][0] = 0;
        }

        //定义重量数组
        List weight = new ArrayList();
        //定义价值数组
        List value = new ArrayList();

        //将首为置为0
        weight.add(0);
        value.add(0);


        //用户输入货物的重量和其对应的价值
        for (int goods = 1; goods <= goodsNumber; goods++) {
            //输入重量
            weight.add(sc.nextInt());
            //输入价值
            value.add(sc.nextInt());
        }


        //开始填写价值矩阵
        int i = 1;
        int j = 1;

        while (j <= capacity) {
            for (; i < weight.size(); i++) {
                if (j < (Integer) weight.get(i)) {
                    valueMartix[i][j] = valueMartix[i - 1][j];
                } else {
                    valueMartix[i][j] = max(valueMartix[i - 1][j], valueMartix[i - 1][j - (Integer) weight.get(i)] + (Integer) value.get(i));
                }
            }
            i = 1;
            j++;

        }

        myPrint(valueMartix,goodsNumber,capacity);
    }

    //比较换与不换两种情况下的价值,返回价值高的数字
    public static int max(int v1, int v2) {
        if (v1 >= v2) {
            return v1;
        } else {
            return v2;
        }
    }
    //输出最佳方案能得到的最大价值
    public static void myPrint(int[][] valueMartix,int goodsNumber,int capacity){
        System.out.println(valueMartix[goodsNumber][capacity]);
    }

}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值