初识动态规划-Dynamic Program

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/shengqianfeng/article/details/102593953

0-1背包问题的Dynamic Program思路

0-1背包问题:对于一组不同重量、不可分割的物品,物品有n个,选择一些物品装入背包,在满足背包最大承重w的前提下求出背包的最大重量。
这个问题可以用回溯算法实现,可以参考《回溯算法》这一篇,也可以使用动态规划的思想去解决。
所谓动态规划,简单说就是把一个问题分为多个阶段,每个阶段对应一个决策Y or N,使用一个状态数组记录记录每一个阶段的状态,根据当前阶段的状态数组,推导出下一个阶段的状态数组,动态地向前推进。

对于背包问题就是,将问题分为n个阶段,n表示物品个数,每个阶段都会决策这个物品是否要放入背包中:放入或者不放入。决策执行之后,背包中的物品重量就会出现多种状态,对应到递归树中就是就是有很多不同节点。我们把递归树每一层中重复的节点合并,只记录不同的状态,然后基于上一层的状态数组,推导出下一阶段的状态数组。每一层中的状态个数最多为w个即背包的承重。使用二维数组states[n][w+1]表示状态数组,记录每个物品决策执行后可以达到的不同状态。

问题分析

这里例子中物品重量为数组:[2,2,4,6,3].

  • 第0个物品的重量是2,要么装入,要么不装入,对应背包重量也有两种状态0或者2,那么就用states[0][0]=true和states[0][2]=true表示两种状态。
  • 第1个物品的重量是2,要么装入,要么不装入,对应背包重量有三种状态0或者2或者4,那么就用states[1][0]=true和states[1][2]=true和states[1][4]=true表示三种状态。
  • 依次类推,把所有物品决策完之后,状态数组states也计算完成,我们只需要在最后一层找一个为true且最接近w的值,就是我们最终的满足w承重下的最大重量。如图:

在这里插入图片描述
代码:

    private int[] weight = {2,2,4,6,3};
    /**
     *  物品个数
     */
    private int n =  5;
    /**
     * 背包承受的最大重量
     */
    private int w = 9;

    /**
     * weight: 物品重量,n: 物品个数,w: 背包可承载重量
     */
    public int f(int[] weight, int n, int w) {
        // 默认值 false
        boolean[][] states = new boolean[n][w+1];
        // 第一行的数据要特殊处理,可以利用哨兵优化
        states[0][0] = true;
        if (weight[0] <= w) {
            states[0][weight[0]] = true;
        }
        // 动态规划状态转移
        for (int i = 1; i < n; ++i) {
            // 不把第 i 个物品放入背包
            for (int j = 0; j <= w; ++j) {
                if (states[i-1][j] == true){
                    states[i][j] = states[i-1][j];
                }
            }
            // 把第 i 个物品放入背包
            for (int j = 0; j <= w-weight[i]; ++j) {
                if (states[i-1][j]==true){
                    states[i][ j + weight[i] ] = true;
                }
            }
        }
        // 输出结果
        for (int i = w; i >= 0; --i) {
            if (states[n-1][i] == true) {
                return i;
            }
        }
        return 0;
    }

时间和空间复杂度分析

时间复杂度从代码的两层循环中可以看出O(n*w),n表示物品个数,w表示总承重。
空间复杂度因为要申请一个n乘以w+1的二维数组,所以内存消耗比较大,所以是空间换时间的思路,不过可以优化。
就是使用一个一维数组来代替二维数组。
优化代码如下:

  /**
     * 动态规划空间复杂度优化(相对于上边的f函数)
     * @param weight: 物品重量 [2,2,4,6,3],n: 物品个数,w: 背包可承载重量 9
     * @return
     */
    public int f2(int[] weight, int n, int w) {
        // 默认值 false
        boolean[] states = new boolean[w + 1];
        // 第一行的数据要特殊处理,可以利用哨兵优化
        states[0] = true;
        if (weight[0] <= w) {
            states[weight[0]] = true;
        }
        // 动态规划
        for (int i = 1; i < n; ++i) {
            // 把第 i 个物品放入背包
            for (int j = w - weight[i]; j >= 0; --j) {
                /**1、j初始值:w-weight[i],往后j--;
                   2、如果j位置的states[j]为true,再放入weight[i]这个物品,j+weight[i]这个位置就被设置为true。
                   否则j位置为false,检查j--这个位置(往前)的状态是否为true。
                 */
                if (states[j] == true) {
                    states[ j + weight[i] ] = true;
                }
            }
        }
        // 输出结果
        for (int i = w; i >= 0; --i) {
            if (states[i] == true) {
                return i;
            }
        }
        return 0;
    }

0-1背包问题的升级

在考虑背包总承重下物品最大重量的前提下,再加入一个物品的价值,计算最终的背包中可以容纳的最大总价值是多少?
代码:

 /**
      物品的总价值
     */
    private int[] value = {3,4,8,9,6};

    /**
     * 在考虑物品价值的情况下,计算在承重限制条件下的背包中最大总价值
     * @param weight 物品总量数组
     * @param value 物品价值数组
     * @param n 物品个数
     * @param w 背包承重
     * @return
     */
    public int superF(int[] weight, int[] value, int n, int w) {
        //states记录当前状态下的物品最大值
        int[][] states = new int[n][w+1];
        // 初始化 states
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < w+1; ++j) {
                states[i][j] = -1;
            }
        }
        states[0][0] = 0;
        if (weight[0] <= w) {
            states[0][ weight[0] ] = value[0];
        }
        // 动态规划,状态转移
        for (int i = 1; i < n; ++i) {
            // 不选择第 i 个物品
            for (int j = 0; j <= w; ++j) {
                if (states[i-1][j] >= 0) {
                    states[i][j] = states[i-1][j];
                }
            }
            // 选择第 i 个物品
            for (int j = 0; j <= w - weight[i]; ++j) {
                //迭代上一行j这个重量的位置的价值大于等于0时的逻辑
                if (states[i-1][j] >= 0) {
                    //当前第i个物品的第j重量所在列的价值,求和第i个物品价值之后的总价值(如果需要,则最终也只应该放在j+weight[i]这个重量位置处)
                    int v = states[i-1][j] + value[i];
                    //第i个物品选择后,除了价值增加,重量也将要增加,增加后重量就是j+weight[i],那么在j+weight[i]这个位置的价值如果小于v,则更新这个位置的价值
                    /**
                     *  之前是重量j的位置设置为true表示放置了重量(然后就更新j+weight[i]为true,表示当前放置后的重量为j+weight[i])
                     *  现在判断v是否大于j+weight[i]大小重量的位置的价值,如果大于那个位置的价值,才会将v放置到那个位置,否则就是小于等于那个位置的价值,没必要更新,继续迭代,j++,
                     */

                    if (v > states[i][ j + weight[i] ]) {
                        states[i][ j + weight[i] ] = v;
                    }
                }
            }
        }
        // 找出最大值
        int maxvalue = -1;
        for (int j = 0; j <= w; ++j) {
            if (states[n-1][j] > maxvalue) {
                maxvalue = states[n-1][j];
            }
        }
        return maxvalue;
    }

猜你喜欢

转载自blog.csdn.net/shengqianfeng/article/details/102593953
今日推荐