【数据结构与算法】动态规划算法的介绍和背包问题程序实现

1. 动态规划算法的介绍

动态规划(Dynamic Programming)算法的核心思想是:

  1. 先将待求解问题分解成若干个子问题。各个子问题不是互相独立的(分治法各个子问题是互相独立的),即下一个子问题的求解是建立在上一个子问题的解的基础上,进行进一步的求解
  2. 再求解子问题,然后从这些子问题的解得到原问题的最优解

2. 背包问题介绍

背包问题是动态规划算法的一个实际应用。一个背包,容量为4磅, 现有如下物品:

物品 重量 价格
吉他(G) 1 1500
音响(S) 4 3000
电脑(L) 3 2000

有如下要求:

  • 要求达到的目标为装入的背包的总价值最大,并且重量不超出背包容量
  • 要求装入的物品不能重复

这里的问题属于01背包,即每个物品最多放一次。另一种是完全背包,每个物品可以放无限次,完全背包可以转化为01背包

3. 通过填表来逐步推进背包问题

第一步:先将问题分解成一个个小的问题,即假设存在背包容量大小分别为1、2、3、4的各种测试容量的背包(分配测试容量的规则为最小物品重量的整数倍,但不超过背包的实际容量)。如下所示,注意这里物品的顺序不重要,因为是一个动态规划的过程,求的是最终的放入最大物品总价值

物品 0磅 1磅 2磅 3磅 4磅
0 0 0 0 0
吉他(G) 0
音响(S) 0
电脑(L) 0

如果背包不能放东西,则放入背包的物品总价值为0,即第一列全为0;如果没有物品,则放入背包的物品总价值也为0,即第一行全为0

第二步:对于第二行(p = 1), 目前只有吉他可以选择,如下所示

物品 0磅 1磅 2磅 3磅 4磅
0 0 0 0 0
吉他(G) 0 1500(G) 1500(G) 1500(G) 1500(G)
音响(S) 0
电脑(L) 0

第三步:对于第三行(p = 2), 目前存在吉他和音响可以选择, 如下所示

物品 0磅 1磅 2磅 3磅 4磅
0 0 0 0 0
吉他(G) 0 1500(G) 1500(G) 1500(G) 1500(G)
音响(S) 0 1500(G) 1500(G) 1500(G) 3000(S)
电脑(L) 0

第四步:对于第四行(p = 3), 目前存在吉他和音响、电脑可以选择, 如下所示

物品 0磅 1磅 2磅 3磅 4磅
0 0 0 0 0
吉他(G) 0 1500(G) 1500(G) 1500(G) 1500(G)
音响(S) 0 1500(G) 1500(G) 1500(G) 3000(S)
电脑(L) 0 1500(G) 1500(G) 2000(L) 3500(L+G)

4. 背包问题的思路分析

用weights数组保存各个物品的重量;用values数组保存各个物品的价值;用dynamicTables二维数组保存动态规划表的逐步推导数据;dynamicTables[p][w]表示前p个物品放入测试容量为w的背包的最大物品总价值,p表示行,即物品所在的行index,w表示列,同时表示背包的测试容量

对不同的物品和不同的背包测试容量进行双层遍历,有如下结果:

  1. dynamicTables[p][0] = dynamicTables[0][w] = 0:表示填入表第一列和第一行都是0
  2. 当weight[p] > w时:dynamicTables[p][w] = dynamicTables[p - 1][w]:表示当前物品的重量大于背包的测试容量,当前物品不能放入背包,所以取前面的物品的最大物品总价值
  3. 当weight[p] <= w时: dynamicTables[p][w] = max{dynamicTables[p - 1][w], weight[p] + dynamicTables[p - 1][w - weight[p]]}:表示当前物品的重量小于等于背包的测试容量,当前物品能放入背包,但不确定放入当前物品是否会让背包的最大物品总价值变大,所以取max{【前面的物品的最大物品总价值】, 【背包先放入当前物品,加上背包剩余的容量放前面的物品的最大物品总价值】}

5. 背包问题的动态规划算法程序实现

public class KnapsackProblem {

    public static void main(String[] args) {
        // 保存物品的重量
        int[] weights = {1, 4, 3};
        // 保存物品的价值
        int[] values = {1500, 3000, 2000};
        // 背包的实际容量
        int knapsackWeight = 4;
        // 物品的种类
        int productCategoryNum = weights.length;
        // 保存动态规划表的逐步推导数据
        int[][] dynamicTables = new int[productCategoryNum + 1][knapsackWeight + 1];
        // 保存物品放入背包的情况。0表示未放入物品,1表示放入物品
        int[][] putPaths = new int[productCategoryNum + 1][knapsackWeight + 1];

        // 填入表第一列和第一行都是0
        for (int p = 0; p < dynamicTables.length; p++) {
            dynamicTables[p][0] = 0;
        }
        for (int w = 0; w < dynamicTables[0].length; w++) {
            dynamicTables[0][w] = 0;
        }

        // 根据公式进行动态规划
        for (int p = 1; p < dynamicTables.length; p++) {
            for (int w = 1; w < dynamicTables[p].length; w++) {
                // 当前物品的重量大于背包的测试容量
                if (weights[p - 1] > w) {
                    dynamicTables[p][w] = dynamicTables[p - 1][w];
                } else {
                    // 当前物品的重量大小于等于背包的测试容量
                    // dynamicTables[p][w] = Math.max(dynamicTables[p - 1][w], values[p - 1] + dynamicTables[p - 1][w - weights[p - 1]]);

                    // 为了记录物品放到背包的情况,不能直接的使用上面的公式,使用if-else来替换
                    if (dynamicTables[p - 1][w] < values[p - 1] + dynamicTables[p - 1][w - weights[p - 1]]) {
                        dynamicTables[p][w] = values[p - 1] + dynamicTables[p - 1][w - weights[p - 1]];

                        // 当前物品放入背包,使背包的物品总价值变大,则表示可以放入该物品,并同时进行记录
                        putPaths[p][w] = 1;
                    } else {
                        dynamicTables[p][w] = dynamicTables[p - 1][w];
                    }
                }
            }
        }

        // 输出dynamicTables
        for (int p = 0; p < dynamicTables.length; p++) {
            for (int w = 0; w < dynamicTables[p].length; w++) {
                System.out.print(dynamicTables[p][w] + " ");
            }
            System.out.println();
        }

        System.out.println("=============倒序输出使背包物品总价值最大的物品===============");
        // 行的最大index
        int p = putPaths.length - 1;
        // 列的最大index
        int w = putPaths[0].length - 1;

        // 从putPaths的最后开始找
        while (p > 0 && w > 0) {
            // 表示找到
            if (putPaths[p][w] == 1) {
                System.out.printf("第%d个物品放入到背包\n", p);
                // 查找到了一个物品,则从前面的物品的最大物品总价值中进行查找
                w -= weights[p - 1];
            }
            // 继续从前面的物品进行查找
            p--;
        }

    }
}

运行程序,结果如下:

0 0 0 0 0 
0 1500 1500 1500 1500 
0 1500 1500 1500 3000 
0 1500 1500 2000 3500 
=============倒序输出使背包物品总价值最大的物品===============
第3个物品放入到背包
第1个物品放入到背包

猜你喜欢

转载自blog.csdn.net/yy8623977/article/details/127257065