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

一、动态规划算法

1.1 基本介绍

  1. 动态规划(Dynamic Programming)算法的核心思想是:将大问题划分为小问题进行解决,从而一步步获取最优解的处理算法
  2. 动态规划算法与分治算法类似,其基本思想也是将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。
  3. 与分治法不同的是,适合于用动态规划求解的问题,经分解得到子问题往往不是互相独立的。 ( 即下一个子阶段的求解是建立在上一个子阶段的解的基础上,进行进一步的求解 )
  4. 动态规划可以通过填表的方式来逐步推进,得到最优解.

1.2 动态规划与分治法的区别:

  • 相同点:基本思想均是将原问题分解成若干个子问题,先求子问题,然后从子问题的解得到原问题的解;
  • 不同点:
    • 适用于动态规划求解的问题,分解得到的子问题往往不是互相独立的;
    • 动态规划为自底向上,而分治法为自顶向下。

二、背包问题

2.1 基本介绍

问题可以描述为:给定一组物品,每种物品都有自己的重量和价格,在限定的总重量内,我们如何选择,才能使得物品的总价格最高。问题的名称来源于如何选择最合适的物品放置于给定背包中。

其中又分01背包(每个物品最多放一个)和完全背包(每种物品都有无限件可用)

首先这是物品的参数:
动态规划算法解决背包问题_第1张图片
每个动态规划算法都从一个网格开始,背包问题的网格如下。

动态规划算法解决背包问题_第2张图片
第一步: 假设能背包能装的物品只有一个吉他,那么不管背包的容量为多少,其背包所能装的价值都是1500。

动态规划算法解决背包问题_第3张图片
第二步: 假设背包能装的物品为4磅的音响和1磅的吉他,当背包的容量为 1、2、3 时,背包中只能放入一个吉他,因为音响的重量为 4,因此无法放入到背包中。
动态规划算法解决背包问题_第4张图片

当背包的容量为 4 时,刚好等于音响的重量。这个时候有两个选择:一是只放入一个吉他;二是只放入一个音响。由于音响的价值为 3000,大于吉他的价值,因此选择将音响放入背包,此时背包中的价值为 3000。

动态规划算法解决背包问题_第5张图片
第三步: 假设背包能装的物品为4磅的音响、1磅的吉他和3磅的电脑,当背包容量为1、2时,背包中只能放一个吉他。

动态规划算法解决背包问题_第6张图片
当背包的容量为 3 时,有两种选择:一是只放入一个吉他;二是只放入一个电脑。由于电脑的价值高于吉他,因此将电脑放入。此时背包的价值为 2000。

动态规划算法解决背包问题_第7张图片
当背包的容量为 4 时,仍然有两种选择:一是放入一个吉他和电脑;二是只放入一个音响。由于吉他和电脑的总价值高于音响的价值,因此将吉他和电脑放入。此时背包的价值为 1500 + 2000 = 3500。

动态规划算法解决背包问题_第8张图片

2.2 思路分析

每次遍历到的第i个物品,设v [ i ] 为第 i 个物品的价值 、w [ i ] 为第 i 个物品的重量,根据 w [ i ] 和 v [ i ] 来确定是否需要将该物品放入背包中。并假设有n个物品,背包的容量为C。再令v [ i ] [ j ] 表示在前i个物品中能够装入容量为 j 的背包中的最大价值。

分析步骤:

  1. 将背包表初始化,v[i][0]=v[0][j]=0 表示前0个物品装入容量为0的背包的最大价值设为0
  2. 当w[i]> j 时,即当新增的商品的重量大于当前背包的容量时,v[i][j] = v[i-1][j],就使用上一个单元格的装入策略
  3. 当j > = w [ i ] 时,即当新增的商品的重量小于等于当前背包的容量时,v[i][j] = max{ v[i-1][j], v[i] + v[i-1][ j-w[i] ] } 则最大价值为当前物品价值加背包剩余容量的最大价值

其中:
v[i-1][j]: 就是上一个单元格的装入的最大价值
v[i] : 表示当前商品的价值
v[i-1][j-w[i]] :在装了第i个物品之后,背包中剩余容量的最大价值

2.3 代码实现

package algorithm;

public class Knapsack {
    public static void main(String[] args) {
        int[] w = {0, 1, 4, 3};    // 物品的重量
        int[] v = {0, 1500, 3000, 2000};    // 物品的价值

        int num = w.length; // 物品的数量可能的个数 (0~3,共 3 个)
        int weight = 5; // 背包的容量可能的个数 (0~4,共 5 个)

        int[][] val = new int[num][weight];    // val[i][j] 表示前i个物品可选,背包容量为j时的最大价值
        int[][] path = new int[num][weight];  // path[i][j] 用于标记第i个物品有没有放入容量为j的背包

        for (int i = 1; i < num; i++) {  // 遍历物品
            for (int j = 1; j < weight; j++) {   // 遍历背包容量
                if (w[i] > j) {    // 如果当前物品重量大于背包容量
                    val[i][j] = val[i - 1][j];  // 不装入
                } else {  // 如果当前物品重量小于等于背包容量
                    int value_1 = val[i - 1][j]; // 不放入第 i 个物品的背包价值
                    int value_2 = v[i] + val[i - 1][j - w[i]]; // 放入第 i 个物品后的价值
                    /* 把最大价值放入背包 */
                    if (value_1 > value_2) {
                        val[i][j] = value_1;
                    } else {
                        val[i][j] = value_2;
                        path[i][j] = 1;   // 用于标记在本网格放入了第 i 个物品
                    }
                }
            }
        }
        // 最后一个网格就是最大价值
        System.out.println("背包价值为:" + val[num - 1][weight - 1]);

        // 由于最后一个网格就是最大价值
        // 因此,逆序遍历 record 找到最后一个放入的物品,然后找到剩余空间价值是放入第几个物品
        int i = path.length - 1;
        int j = path[0].length - 1;
        while (i > 0 && j > 0) {
            if (path[i][j] == 1) {
                System.out.printf("放入了第 %d 个商品\n", i);
                j = j - w[i];   // 背包剩余容量
            }
            i--;
        }
    }
}

你可能感兴趣的:(数据结构与算法)