动态规划算法

一、算法介绍

动态规划Dynamic Programming,DP)是运筹学的一个分支,是求解决策过程最优化的过程。

  • 应用场景:动态规划的应用极其广泛,包括工程技术、经济、工业生产、军事以及自动化控制等领域,并在背包问题、生产经营问题、资金管理问题、资源分配问题、最短路径问题和复杂系统可靠性问题等中取得了显著的效果。

  • 核心思想:将大问题划分为小问题进行解决,从而一步步获取最优解的处理算法。

  • 分治算法的异同点

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

二、经典案例-背包问题

2.1 需求分析

背包问题Knapsack problem)是一种组合优化的NP完全问题。问题可以描述为:给定一组物品,每种物品都有自己的重量价格,在限定的总重量内,我们如何选择,才能使得物品的总价格最高

  • 题目说明:有一个背包,总容量为 4 磅 , 现有如下物品:
物品重量价格
吉他(G)11500
音响(S)43000
电脑(L)32000
  • 需求一:要求达到的目标为装入的背包的总价格最大,并且重量不超出
  • 需求二:要求装入的物品不能重复
  • 补充说明:背包问题又分为01背包(每个物品最多放一个)和完全背包(每种物品都有无限件可用)。接下来我们会根据01背包的要求解决问题。

2.2 算法步骤

  • 算法思路

    • 每次遍历到的第 i 个物品,根据 w[i] 和 v[i] 来确定是否需要将该物品放入背包中。(即对于给定的 n 个物品,设 v[i]、w[i]分别为第 i 个物品的价格和重量,c 为背包的容量)。
    • 再令 v[i][j]表示在前 i 个物品中能够装入容量为 j 的背包中的最大价格(动态变化)。
  • 具体步骤如下

  1. 填入表(第一行和第一列是 0):v[i][0]=v[0][j]=0;
  2. 当准备加入新增的商品的容量大于当前背包的容量时,就直接使用上一个单元格的装入策略:当 w[i] > j 时:v[i][j]=v[i-1][j];
  3. 当准备加入的新增的商品的容量小于等于当前背包的容量时:当 w[i] <= j 时: 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-1 商品,到剩余空间 j-w[i]的最大值 )。
  • 示意图

在这里插入图片描述

2.3 代码示例

public class KnapsackProblemDemo {

    public static void main(String[] args) {

        // 物品的重量。
        int[] w = {1, 4, 3};
        // 物品的价格(这里val[i] 就是前面讲的v[i])。
        int[] val = {1500, 3000, 2000};
        // 背包容量。
        int m = 4;
        // 物品数量。
        int n = val.length;

        // 【第一步】:初始化操作。
        // 表示在前i个物品中能够装入容量为j的背包中的最大价格。
        // 行为物品数量,列为背包容量。
        int[][] v = new int[n + 1][m + 1];
        // 用于记录商品放入情况。
        int[][] records = new int[n + 1][m + 1];

        // 初始化填入表(全0的行和列)。
        for (int i = 0; i < v.length; i++) {
            // 第一列设置为0。
            v[i][0] = 0;
        }
        // 第一行设置为0.
        Arrays.fill(v[0], 0);


        // 【第二步】:根据公式进行动态规划处理。
        for (int i = 1; i < v.length; i++) {
            for (int j = 1; j < v[0].length; j++) {
                // 如果新增商品重量大于当前背包的容量,就使用上一个单元格的策略。
                // 说明1:程序i是从1开始的,因此原来公式中的 w[i] 修改成 w[i-1]。
                if (w[i - 1] > j) {
                    v[i][j] = v[i - 1][j];
                } else {
                    // v[i-1][j]:表示上一个单元格的装入的最大值。
                    // v[i]:表示当前商品的价格。
                    // v[i-1][j-w[i]]:表示装入 i-1 商品,到剩余空间 j-w[i]的最大值。
                    // 说明2:同样地,程序i是从1开始的,因此原来公式中的 w[i] 修改成 w[i-1]。
                    if (v[i - 1][j] < val[i - 1] + v[i - 1][j - w[i - 1]]) {
                        v[i][j] = val[i - 1] + v[i - 1][j - w[i - 1]];
                        // 记录当前情况。
                        records[i][j] = 1;
                    } else {
                        // 否则使用上一个单元格的策略。
                        v[i][j] = v[i - 1][j];
                    }
                }
            }
        }

        // 输出当前记录的情况。
        for (int[] i : v) {
            for (int j : i) {
                System.out.print(j + " ");
            }
            System.out.println();
        }
        // 0 0 0 0 0
        // 0 1500 1500 1500 1500
        // 0 1500 1500 1500 3000
        // 0 1500 1500 2000 3500

        System.out.println("============================");

        // 查看最后单元格策略是否符合预期。
        // 行、列的最大下标。
        int maxRow = records.length - 1;
        int maxCol = records[0].length - 1;

        System.out.println("最后单元格放入总价格=" + v[maxRow][maxCol]);
        System.out.println("具体策略如下:");

        while (0 < maxRow && 0 < maxCol) {
            if (1 == records[maxRow][maxCol]) {
                System.out.printf("第%d个商品放入到背包。", maxRow);
                System.out.println();
                maxCol -= w[maxRow - 1];
            }
            maxRow--;
        }
        // 最后单元格放入总价格=3500
        // 具体策略如下:
        // 第3个商品放入到背包。
        // 第1个商品放入到背包。
    }
}

三、结束语


“-------怕什么真理无穷,进一寸有一寸的欢喜。”

微信公众号搜索:饺子泡牛奶

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值