动态规划思想案例刨析

这篇具有很好参考价值的文章主要介绍了动态规划思想案例刨析。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

动态规划的思想

动态规划解决问题的核心思想是“重叠子问题”和“最优子结构”。

重叠子问题:在复杂问题中,往往存在许多重复的子问题。动态规划通过避免重复计算,将子问题的解保存起来,以便在需要时直接引用,从而提高效率。通过记忆化存储或者使用动态规划表来实现。

最优子结构:如果一个问题的最优解包含了其子问题的最优解,那么我们称这个问题具有最优子结构。动态规划利用最优子结构的性质,将问题划分为一系列规模较小的子问题,通过求解子问题的最优解来得到原问题的最优解。

动态规划的应用步骤

使用动态规划解决问题一般包括以下步骤:

  1. 定义状态:明确问题的状态,即问题的子问题是什么,以及如何表示子问题的状态。状态的选择通常与问题的特性相关。

  2. 确定状态转移方程:根据问题的最优子结构,确定子问题之间的关系,即如何通过子问题的最优解来求解原问题的最优解。这个关系可以用状态转移方程来表示。

  3. 确定初始条件和边界情况:确定初始状态和边界情况,即最简单的子问题的解。

  4. 计算顺序:确定计算子问题的顺序,通常是自底向上或者自顶向下的方式。

  5. 计算最优解:根据状态转移方程和初始条件,计算子问题的最优解,并逐步计算得到原问题的最优解。

动态规划的应用案例

动态规划可以应用于各种问题领域,如:

  • 背包问题:0-1背包问题、完全背包问题等。
  • 最短路径问题:迪杰斯特拉算法、弗洛伊德算法等。
  • 编辑距离问题:计算两个字符串之间的最小编辑操作次数。
  • 斐波那契数列:通过动态规划的方式计算斐波那契数列的第n项。
  • 矩阵链乘法:计算矩阵相乘的最优顺序。

具体代码分析
1. 背包问题(Knapsack Problem):

背包问题是一个经典的优化问题,可以分为01背包问题和完全背包问题。这里以01背包问题为例,即每个物品只能选择放入背包一次或不放入。

1,动态规划解决01背包问题的代码示例:

public class KnapsackProblem {
    public static int knapsack(int[] weights, int[] values, int capacity) {
        int n = weights.length;
        int[][] dp = new int[n + 1][capacity + 1];

        // 初始化第一行和第一列为0,表示没有物品或容量为0时的最大价值为0
        for (int i = 0; i <= n; i++) {
            dp[i][0] = 0;
        }
        for (int j = 0; j <= capacity; j++) {
            dp[0][j] = 0;
        }

        // 动态规划求解
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= capacity; j++) {
                if (weights[i - 1] <= j) {
                    // 当前物品的重量小于等于背包容量,可以选择放入或不放入背包
                    dp[i][j] = Math.max(values[i - 1] + dp[i - 1][j - weights[i - 1]], dp[i - 1][j]);
                } else {
                    // 当前物品的重量大于背包容量,不能放入背包
                    dp[i][j] = dp[i - 1][j];
                }
            }
        }

        return dp[n][capacity];
    }

    public static void main(String[] args) {
        int[] weights = {2, 3, 4, 5};
        int[] values = {3, 4, 5, 6};
        int capacity = 8;
        int maxVal = knapsack(weights, values, capacity);
        System.out.println("背包能够装下的最大价值为:" + maxVal);
    }
}

代码解释:

  • weights数组存储物品的重量,values数组存储物品的价值,capacity表示背包的容量。
  • dp是一个二维数组,dp[i][j]表示前i个物品在背包容量为j时的最大价值。
  • 动态规划的核心思想是通过填充dp数组来逐步计算最优解。
  • 外部两层循环用于遍历每个物品和每个背包容量。
  • 内部的条件判断根据当前物品的重量,决定是否放入背包以获得最大价值。
  • 最终返回dp[n][capacity],即前n个物品在背包容量为capacity时的最大价值。
    完全背包问题是一个经典的动态规划问题,它可以描述为在给定背包容量和一组物品的情况下,选择物品放入背包,使得背包中物品的总价值最大化。与0-1背包问题不同的是,完全背包问题中每个物品可以选择无限次放入背包。

2,完全背包问题的动态规划求解方法:

假设有N个物品,它们的重量分别为w[1], w[2], …, w[N],价值分别为v[1], v[2], …, v[N],背包的容量为C。我们定义一个二维数组dp[N+1][C+1],其中dp[i][j]表示在前i个物品中选择,且背包容量为j时的最大总价值。

初始化dp数组中的所有元素为0。然后我们从前往后遍历物品,对于每个物品i,从容量0到C依次计算dp[i][j]的值。

对于dp[i][j]的计算,有两种情况:

  1. 不选择当前物品i:dp[i][j] = dp[i-1][j],即背包容量为j时,前i个物品的最大总价值与前i-1个物品的最大总价值相同。
  2. 选择当前物品i:dp[i][j] = dp[i][j-w[i]] + v[i],即背包容量为j时,考虑物品i放入背包,此时总价值为dp[i][j-w[i]](在当前物品i的基础上减去物品i的重量w[i],背包容量减少),再加上物品i的价值v[i]。

综合以上两种情况,dp[i][j]的最大值即为dp[i-1][j]和dp[i][j-w[i]] + v[i]的较大值。

最终,dp[N][C]即为问题的解,表示在前N个物品中选择,且背包容量为C时的最大总价值。

以下是完全背包问题的代码示例(使用Java语言):

public class Knapsack {
    public static int knapsack(int[] weights, int[] values, int capacity) {
        int n = weights.length;
        int[][] dp = new int[n + 1][capacity + 1];

        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= capacity; j++) {
                if (weights[i - 1] <= j) {
                    dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - weights[i - 1]] + values[i - 1]);
                } else {
                    dp[i][j] = dp[i - 1][j];
                }
            }
        }

        return dp[n][capacity];
    }

    public static void main(String[] args) {
        int[] weights = {2, 3, 4, 5};
        int[] values = {3, 4, 5, 6};
        int capacity = 8;

        int maxTotalValue = knapsack(weights, values, capacity);
        System.out.println("背包中物品的最大总价值为:" + maxTotalValue);
    }
}

这个示例代码中,weights数组和values数组分别表示物品的重量和价值,capacity表示背包的容量。最后输出的maxTotalValue即为背包中物品的最大总价值。

2. 打家劫舍问题(House Robber Problem):

打家劫舍问题是一个经典的动态规划问题,可以形象地描述为在一条街上的房屋中选择一些房屋进行盗窃,但不能同时盗窃相邻的房屋。目标是盗窃到的金额最大。

以下是用动态规划解决打家劫舍问题的代码示例:

public class HouseRobber {
    public static int rob(int[] nums) {
        int n = nums.length;
        if (n == 0) {
            return 0;
        }
        if (n == 1) {
            return nums[0];
        }

        int[] dp = new int[n];
        dp[0] = nums[0];
        dp[1] = Math.max(nums[0], nums[1]);

        for(int i = 2; i < n; i++) {
            dp[i] = Math.max(nums[i] + dp[i - 2], dp[i - 1]);
        }

        return dp[n - 1];
    }

    public static void main(String[] args) {
        int[] nums = {1, 2, 3, 1};
        int maxAmount = rob(nums);
        System.out.println("能够盗窃到的最大金额为:" + maxAmount);
    }
}

代码解释:

  • nums数组存储每个房屋中的金额。
  • dp数组存储从第一个房屋到当前房屋的最大金额。
  • 初始化dp[0]为第一个房屋的金额,dp[1]为第一个和第二个房屋中金额较大的一个。
  • 从第三个房屋开始,每次选择盗窃当前房屋和前两个房屋中金额较大的一个,将结果存入dp[i]
  • 最终返回dp[n - 1],即最后一个房屋的最大金额。

3. 最长递增子序列(Longest Increasing Subsequence):

最长递增子序列问题是要找到给定序列中的最长递增子序列的长度。以下是用动态规划解决最长递增子序列问题的代码示例:

public class LongestIncreasingSubsequence {
    public static int lengthOfLIS(int[] nums) {
        int n = nums.length;
        int[] dp = new int[n];
        Arrays.fill(dp, 1);

        for (int i = 1; i < n; i++) {
            for (int j = 0; j < i; j++) {
                if (nums[i] > nums[j]) {
                    dp[i] = Math.max(dp[i], dp[j] + 1);
                }
            }
        }

        int maxLength = 0;
        for (int len : dp) {
            maxLength = Math.max(maxLength, len);
        }

        return maxLength;
    }

    public static void main(String[] args) {
        int[] nums = {10, 9, 2, 5, 3, 7, 101, 18};
        int maxLength = lengthOfLIS(nums);
        System.out.println("最长递增子序列的长度为:" + maxLength);
    }
}

代码解释:

  • nums数组存储给定的序列。
  • dp数组用于记录每个位置上的最长递增子序列长度,初始值都为1。
  • 外部两层循环用于遍历每个位置,并比较当前位置与之前位置的大小关系。
  • 如果当前位置的值大于之前位置的值,则更新当前位置上的最长递增子序列长度为之前位置中最大长度加1。
  • 最终返回dp数组中的最大值,即为最长递增子序列的长度。

4. 矩阵连乘积问题(Matrix Chain Multiplication):

矩阵连乘积问题是一个经典的动态规划问题,要求找到一种最优的矩阵相乘顺序,使得整个连乘的计算量最小。以下是用动态规划解决矩阵连乘积问题的代码示例:

public class MatrixChainMultiplication {
    public static int matrixChainOrder(int[] dimensions) {
        int n = dimensions.length - 1;
        int[][] dp = new int[n][n];

        for (int len = 2; len <= n; len++) {
            for (int i = 0; i < n - len + 1; i++) {
                int j = i + len - 1;
                dp[i][j] = Integer.MAX_VALUE;

                for (int k = i; k < j; k++) {
                    int cost = dp[i][k] + dp[k + 1][j] + dimensions[i] * dimensions[k + 1] * dimensions[j + 1];
                    if (cost < dp[i][j]) {
                        dp[i][j] = cost;
                    }
                }
            }
        }

        return dp[0][n - 1];
    }

    public static void main(String[] args) {
        int[] dimensions = {10, 30, 5, 60};
        int minCost = matrixChainOrder(dimensions);
        System.out.println("最小的矩阵连乘积计算量为:" + minCost);
    }
}

代码解释:

  • dimensions数组存储矩阵的维度信息,如[10, 30, 5, 60]表示有三个矩阵,维度分别为10x30、30x5和5x60。
  • dp数组用于记录每个子问题的最小计算量。
  • 外部两层循环用于遍历子问题的长度,从2开始逐步增加。
  • 内部的循环用于遍历每个子问题的起始位置和结束位置,并计算当前情况的最小计算量。
  • 在内部循环中,通过尝试不同的划分点,计算出将两个子问题相乘的计算量,并选择最小的计算量作为当前子问题的最优解。
  • 最终返回dp[0][n - 1],即整个矩阵连乘的最小计算量。

总结

动态规划是一种将复杂问题化繁为简的求解方法。通过将问题划分为一系列子问题,并通过求解子问题的最优解来得到原问题的最优解。动态规划的核心思想是重叠子问题和最优子结构。通过定义状态、确定状态转移方程、确定初始条件和边界情况、计算顺序以及计算最优解这几个步骤,我们可以有效地应用动态规划解决各种问题。文章来源地址https://www.toymoban.com/news/detail-796387.html

到了这里,关于动态规划思想案例刨析的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 算法思想—枚举、递推、迭代、递归、分治、贪心、动态规划、回溯、模拟、分支定界

    算法思想 枚举(暴力算法) 枚举算法(暴力算法)是一种通过逐一尝试所有可能解来解决问题的算法。它的基本思想是将问题的所有可能答案一一列举出来,并根据一定的判断条件来确定哪些答案是合适的。这种算法通常使用循环来实现,因为需要尝试所有可能的情况。两

    2024年02月01日
    浏览(39)
  • C++--动态规划其他问题

    1.一和零  力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台 给你一个二进制字符串数组 strs 和两个整数 m 和 n 。 请你找出并返回 strs 的最大子集的长度,该子集中 最多 有 m 个 0 和 n 个 1 。 如果 x 的所有元素也是 y 的元素,集合 x 是集合 y 的 子集 。 示例 1: 示例 2:

    2024年02月10日
    浏览(33)
  • 设计模式之代理模式(静态代理&动态代理)

    目录 1、什么是代理模式 2、代理模式的结构 3、代理模式的实现 3.1 静态代理和动态代理概念 3.2 静态代理 3.3 动态搭理 3.3.1 代码实现 3.3.2 Proxy类讲解 4、动态代理VS静态代理 5、代理模式优缺点 由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象

    2024年02月03日
    浏览(33)
  • 设计模式8:代理模式-动态代理

    上一篇:设计模式8:代理模式-静态代理 “动态”的含义是 代码生成代理类,一个代理类可以代理多个接口 。 动态区别于 死板 ,静态代理中一个代理类只能代理一个接口,其他不同的接口,就需要再手写不同的代理类,这就很 死板 。 动态代理类似于在安卓里面,我们常

    2024年02月09日
    浏览(31)
  • 代理模式--静态代理和动态代理

    定义:代理模式就是代替对象具备真实对象的功能,并代替真实对象完成相应的操作并且在不改变真实对象源代码的情况下扩展其功能,在某些情况下,⼀个对象不适合或者不能直接引⽤另⼀个对象,⽽代理对象可以在客户端和⽬标对象之间起到中介的作⽤ 使用代理模式可以

    2024年02月15日
    浏览(39)
  • 温故知新之:代理模式,静态代理和动态代理(JDK动态代理)

    代理模式可以在不修改被代理对象的基础上,通过扩展代理类,进行一些功能的附加与增强。 静态代理 是一种代理模式的实现方式,它在编译期间就已经确定了代理对象,需要为每一个被代理对象创建一个代理类。静态代理的实现比较简单,但是每个被代理对象都需要创建

    2024年02月11日
    浏览(47)
  • 代理设计模式——静态代理和动态代理

    代理模式 在代理模式(Proxy Pattern)中,一个类代表另一个类的功能。这种类型的设计模式属于结构型模式,在代理模式中,我们创建具有现有对象的对象,以便向外界提供功能接口。 意图: 为其他对象提供一种代理以控制对这个对象的访问。 主要解决: 在直接访问对象时

    2024年02月13日
    浏览(43)
  • Java代理模式——静态代理与动态代理

    代理模式允许你为其他对象提供一个代理,以控制对这个对象的访问。代理模式在不改变实际对象的情况下,可以在访问对象时添加额外的功能。 可以理解为代理模式为被代理对象创造了一个替身,调用者可以通过这个替身去实现这个被代理对象的功能,这个替身也可以为被

    2024年02月13日
    浏览(45)
  • 代理模式:静态代理+JDK/CGLIB 动态代理

    代理模式是一种比较好理解的设计模式。简单来说就是 我们使用代理对象来代替对真实对象(real object)的访问,这样就可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标对象的功能。 代理模式的主要作用是扩展目标对象的功能,比如说在目标对象的某个方法

    2024年02月13日
    浏览(35)
  • 【C++ 观察者模式 思想理解】C++中的观察者模式:松耦合设计与动态交互的艺术,合理使用智能指针观察者

    在进入技术细节之前,理解观察者模式(Observer Pattern)的基本概念和它在现代编程中的重要性是至关重要的。 观察者模式是一种设计模式,它定义了对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动更新。在C++中,这个

    2024年01月24日
    浏览(54)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包