动态规划之0/1背包问题

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/huangzhilin2015/article/details/78821618

网上介绍动态规划和背包问题的文章多不胜数,这里就不再去粘贴复制哪些概念性的东西了,直接切入主题。

先将0/1背包问题的分析简单概括一下,我从正反两个方向阐述:

场景:现在有若干个物品,每个物品有自己的重量和价值,背包有一定的容量,在背包能容纳的下的情况下随便装入物品(物品不可分割),要求获取的价值要最大化。(本文请忽略背包用"容量"来装"重量"~~,还有为了通俗,也尽量少用数学符号)

正向分析如果前i个物品在背包容量为j时,我们获取到了最优解,达到了最大价值(定义此最大价值为V),如果再多一个物品(给这个物品一个标识:x,重量为wx),背包容量不变仍为j,问题变成了,前i+1个物品在背包容量为j时的最大价值,那么为多少呢?

对于x物品来说,它的命运只有两个:放/不放。

我们首先需要考虑一个最基本的问题,这个容量j能否放的下x物品?不然说再多都是浪费表情,如果放不下,那么很简单,我们直接不理它,此时即使多了x物品,最大价值也=V。

如果放得下,我们再来考虑放还是不放(我们要价值最大化,所以取两者间的最大值)

1>如果不放,那么很简单,我们直接不理他,此时得到一个价值V1(显然V1==V)

2>如果放,那我们就需要重新分配这个背包,此时的最大价值为:x的价值(要放x,肯定得算上它的价值噻) + 前i个物品(这些物品我们没有动,该咋的咋的)在容量为 j-wx(放入x后,背包的可分配容量只剩j-wx)时的最大价值,我们定义为V2。所以,此时最大价值=V1 > V2 ? V1 : V2

反向分析上面的正向分析,直接假设了前i个物品在背包容量为j时的最优解V,那么我们来考虑V=啥?

我们认为i物品的重量为wi,其实和上面的分析一样,V=第i个物品的价值 + 前i-1个物品在容量为j-wi时的最大价值,等等,这个等式成立的条件是什么?

没错,前提是我们为了获取最优解,是将i物品放入背包了的,而i物品放入背包的条件又是什么?当然是容量能够容纳i,并且在能够容纳i的情况下,放i比不放i,最终的价值更大。

下面列出代码(java,随便写的测试代码,看个意思就行了,仅供参考~~)

正向:嵌套for循环方式,此方式为从最小子问题(0个物品,背包容量为0)开始到最终解(i个物品,背包容量为j)。使用二维数组保存子问题最优解

反向:递归

package com.loren;

/**
 * Created by loren on 2017/12/16.
 */
public class Test01BeiBao {
    //物品数量
    static int num = 4;
    //每个物品的价值数组
    static int value[] = {10, 40, 30, 50};
    //每个物品的重量数组和value一一对应
    static int weight[] = {5, 4, 6, 3};
    //背包容量
    static int W = 10;

    public static void main(String[] args) {
        System.out.println("for循环-结果:" + getResult(num, value, weight, W));
        System.out.println("递归-结果:" + getResultDG(num - 1, W));
    }

    /**
     * 获取结果(递归版)
     *
     * @param i 物品下标从0开始,表示前i个物品
     * @param j
     * @return
     */
    public static int getResultDG(int i, int j) {
        if (j == 0) {
            //背包容量为0时,最大价值=0
            return 0;
        }
        if (i == 0) {
            //递归到前0个物品时,表示背包容量为j,只有第一个物体可选时的最大价值
            //如果j容量能够容纳下该物品,返回该物品的价值,否则返回0
            if (j >= weight[0]) {
                return value[0];
            } else {
                return 0;
            }
        }
        //前i个物品在容量为j时的最大价值=i-1个物品在容量为j-weight[i]的最大价值+value[i]
        //这个结论的前提是为了达到最大价值,是将第i个物品放入背包了的,否则最大价值=i-1个物品在容量为j时的价值
        //而第i个物品是否放入背包的判断条件是:背包容量允许放入此物品且放入后的价值比不放的价值高
        //判断是否需要放入此物品
        if (weight[i] <= j) {
            int a = getResultDG(i - 1, j - weight[i]) + value[i];
            int b = getResultDG(i - 1, j);
            return a > b ? a : b;
        } else {
            return getResultDG(i - 1, j);
        }
    }

    /**
     * @param value
     * @param weight
     * @param W      背包重量
     * @return
     */
    public static int getResult(int num, int[] value, int[] weight, int W) {
        //初始化二维数组,用于保存i个物品在容量为j是的最大价值
        int[][] tab = new int[num + 1][W + 1];//整个二维数组需要包含0个物品在容量为j时和i个物品在容量为0时的最大价值,需要
        //初始化数组,0个物品在容量为j时和i个物品在容量为0时的最大价值均为0
        //之所以初始化tab[0][i]和tab[i][0],是为了在计算到第一个物品时方便,便于理解
        for (int i = 0; i <= W; i++) {
            tab[0][i] = 0;
        }
        for (int i = 0; i <= num; i++) {
            tab[i][0] = 0;
        }
        for (int i = 1; i <= num; i++) {
            //外层循环每个物品
            for (int j = 1; j <= W; j++) {
                //内层循环当前背包容量
                if (weight[i - 1] <= j) {
                    //如果当前物品能够放进背包,有两种情况:放或者不放,当前最大价值只需要取两种情况之间的最大值即可
                    //放:最大价值=当前物品的价值+(i-1)个物品在(当前容量-当前容量)时的最大价值
                    int v1 = value[i - 1] + tab[i - 1][j - weight[i - 1]];
                    //不放:最大价值=(i-1)个物品在当前容量时的最大价值
                    int v2 = tab[i - 1][j];
                    //取最大值
                    tab[i][j] = v1 >= v2 ? v1 : v2;
                } else {
                    //如果当前物品不能放进背包,那么价值就为i-1个物品(除去当前物品)在j容量时的价值
                    tab[i][j] = tab[i - 1][j];
                }
            }
        }
        //输出整个二维数组
        System.out.print("                           ");
        for (int i = 0; i <= W; i++) {
            System.out.print("容量:" + i + "       ");
        }
        System.out.println();
        for (int i = 0; i < tab.length; i++) {
            System.out.print("物品:" + i + "value=" + (i > 0 ? value[i - 1] : 0) + ",weight=" + (i > 0 ? weight[i - 1] : 0) + (i == 0 ? "       " : "      "));
            for (int j = 0; j < tab[0].length; j++) {
                System.out.print(tab[i][j] + (tab[i][j] >= 10 ? "           " : "            "));
            }
            System.out.println();
        }
        System.out.println("----------------------");
        return tab[value.length][W];//返回所有物品在容量为W时的最大价值

    }

}

猜你喜欢

转载自blog.csdn.net/huangzhilin2015/article/details/78821618