动态规划,终于有人讲明白了

58 篇文章 1 订阅

        动态规划是计算机科学中的一个非常重要的概念,特别是在解决优化问题时,它显示出了极大的效率。本文详细介绍动归及其特性、优化等,结合实际案例配套代码深入剖析 。

目录

动态规划简介

动态规划与其他算法的区别

应用场景

动态规划的核心元素

最优子结构

边界条件

状态转移方程

动态规划的实现步骤

问题建模

确定状态与状态空间

确定决策并推导状态转移方程

边界条件的设定

编写代码实现

Python实现动态规划

示例问题介绍:斐波那契数列

Python代码解析

动态规划的优化技巧

空间优化

时间优化

记忆化搜索

实战案例分析

背包问题

最长公共子序列(LCS)

其他经典问题


动态规划简介

        动态规划(Dynamic Programming, DP)是一种算法思想,它将复杂问题分解为小问题,通过解决这些小问题来解决整个大问题。动态规划通常用于求解最优化问题,如最短路径问题、最大子序和问题等。动态规划的核心是记住已解决的子问题的答案,通过这种方式避免了大量的重复计算,极大提高了算法的效率。

动态规划与其他算法的区别

        动态规划与其他算法,如贪心算法、分治算法等,最大的区别在于其处理问题的方式。贪心算法每一步都采取当前状态下的最优解,而不考虑整体最优解;分治算法将问题分解为独立的子问题进行解决。相比之下,动态规划通过将问题分解为相互依赖的子问题来找到整体最优解。动态规划适用于有重叠子问题和最优子结构性质的问题。

应用场景

        动态规划的应用范围非常广泛,从经典的算法问题到实际应用问题都有动态规划的身影。比如,在计算机科学领域,动态规划常用于字符串匹配、图算法、资源分配等问题。在经济学、运筹学中,动态规划也被用来解决资源最优分配、生产计划等问题。

动态规划的核心元素

        动态规划作为解决优化问题的重要工具,其高效之处在于合理利用了问题的结构特性,这些结构特性主要包括最优子结构、边界条件和状态转移方程。

最优子结构

        最优子结构是指一个问题的最优解包含其子问题的最优解。简单来说,就是大问题的最优解可以通过小问题的最优解推导出来。这一特性是动态规划能够成功应用的关键。比如,在求解最短路径问题时,从点A到点C的最短路径,如果经过点B,那么点A到点B的路径也必须是最短的,这就是最优子结构的体现。

边界条件

        边界条件是动态规划中定义问题解的基础条件。在动态规划中,通常将最小的子问题定义为边界条件,这些最小的子问题是可以直接求解的,无需进一步分解。边界条件的正确设置是保证动态规划正确性的基础。例如,在斐波那契数列中,边界条件可以定义为F(0)=0F(1)=1

状态转移方程

        状态转移方程是动态规划中最核心的元素之一,它描述了问题状态之间的转移关系。在动态规划中,每个状态通常代表了一个或一组子问题的解,状态转移方程定义了如何从一个状态(或多个状态)转移至另一个状态。状态转移方程的确定需要对问题有深刻的理解,通常包括状态的定义、状态之间的转移以及转移时的决策过程。

        例如,在背包问题中,状态转移方程可以表达为:

dp[i][w] = max(dp[i-1][w], dp[i-1][w-weights[i]] + values[i])

其中dp[i][w]表示考虑前i个物品,当前背包容量为w时的最大价值。

动态规划的实现步骤

        实现动态规划通常需要经历以下几个关键步骤:问题建模、确定状态与状态空间、确定决策并推导状态转移方程、边界条件的设定,以及编写代码实现。每一步都至关重要,缺一不可。

问题建模

        问题建模是动态规划实现过程中的第一步,其目标是将实际问题转化为一个可以通过动态规划求解的模型。这需要对问题有深刻的理解,包括问题的目标、限制条件以及解题的可能方法。问题建模的成功与否直接关系到后续步骤的顺利进行。

确定状态与状态空间

        在动态规划中,状态是描述问题解的关键信息,而状态空间则是所有状态的集合。确定状态与状态空间是实现动态规划的核心步骤之一。状态的选择需要能够反映问题解的变化,同时状态空间的大小也直接影响到算法的时间和空间复杂度。合理定义状态和状态空间是高效解决问题的关键。

确定决策并推导状态转移方程

        确定了状态后,下一步就是确定在各个状态之间如何进行转移,即决策过程。这通常需要根据问题的具体情况来定。状态转移方程则是动态规划中最为核心的部分,它描述了状态之间的转移关系,是实现动态规划的数学基础。正确推导状态转移方程是解题成功的关键。

边界条件的设定

        边界条件定义了最简单的子问题,它们是递归过程中的终止条件。在动态规划中,合理设置边界条件不仅可以保证算法的正确性,还可以避免不必要的计算,提高算法的效率。

编写代码实现

        最后,将上述步骤转化为具体的代码实现。这需要选择合适的数据结构来存储状态和决策过程,并实现状态转移的逻辑。在编码过程中,还需要注意代码的可读性和运行效率。

Python实现动态规划

示例问题介绍:斐波那契数列

        斐波那契数列是一个经典的递归问题,它的每一项都是前两项之和,公式定义为:F(n) = F(n-1) + F(n-2),其中F(0)=0, F(1)=1。虽然斐波那契数列可以简单地通过递归来解决,但递归方法会重复计算许多子问题,效率极低。使用动态规划,我们可以避免这种重复计算,大大提高效率。

Python代码解析
def fibonacci(n):
    # 初始化一个长度为n+1的数组,用于存储斐波那契数列的值
    dp = [0] * (n + 1)
    
    # 设置边界条件
    dp[0], dp[1] = 0, 1
    
    # 通过循环从2计算到n,填充dp数组
    for i in range(2, n + 1):
        dp[i] = dp[i - 1] + dp[i - 2]
    
    # 返回斐波那契数列的第n项
    return dp[n]

        这段代码首先初始化一个长度为n+1的数组dp,其中dp[i]用来存储斐波那契数列的第i项。然后设置斐波那契数列的边界条件,即F(0)和F(1)。接着,通过一个循环从2计算到n,利用状态转移方程F(n) = F(n-1) + F(n-2)来填充dp数组。最后,函数返回dp[n],即斐波那契数列的第n项。

动态规划的优化技巧

空间优化

        在许多动态规划问题中,当前状态只依赖于前几个状态,这意味着我们不需要存储整个状态数组,只需要存储必要的几个状态即可。这种方法可以显著减少动态规划所需的空间复杂度。以斐波那契数列为例,我们可以只用两个变量来存储前两个状态,而不是使用一个数组来存储所有计算过的斐波那契数。

def fibonacci_optimized(n):
    if n <= 1:
        return n

    prev, curr = 0, 1
    for i in range(2, n + 1):
        prev, curr = curr, prev + curr

    return curr

        在这个优化版本中,prevcurr变量分别存储了前两个状态的值,每次迭代时都更新这两个变量。这样,空间复杂度从O(n)降低到了O(1)。

时间优化

        时间优化通常涉及到避免不必要的计算,尤其是避免重复计算已经解决的子问题。记忆化技术是一种常见的时间优化方法,它通过存储已经计算过的子问题的结果来避免重复计算。在递归实现的动态规划中,这种技术尤为有用。

def fibonacci_memo(n, memo={}):
    if n in memo:
        return memo[n]
    if n <= 1:
        return n

    memo[n] = fibonacci_memo(n - 1, memo) + fibonacci_memo(n - 2, memo)
    return memo[n]

        这里使用一个字典memo来存储已经计算过的斐波那契数,如果当前要计算的斐波那契数已经在字典中,就直接返回其值,避免了重复计算。

记忆化搜索

        记忆化搜索是一种将递归算法优化成动态规划算法的技术。它通过建立一个全局查找表来存储所有已解决的子问题的结果。每次递归调用前,都先查找表中是否已有当前子问题的解,如果有,则直接返回该解,避免重复计算。这可以看作是自顶向下的动态规划实现,与传统的自底向上的动态规划实现相对应。

实战案例分析

背包问题

        背包问题是动态规划中的一个经典问题,它描述了这样一个场景:给定一组物品,每种物品都有自己的重量和价值,在限定的总重量内,我们应如何选择装入背包的物品,使得背包中物品的总价值最大?

        这个问题可以通过动态规划来解决。我们定义一个二维数组dp[i][w],其中i表示考虑到第i个物品时,w表示当前背包的重量限制。dp[i][w]的值表示在当前限制下能装入的最大价值。状态转移方程为:dp[i][w] = max(dp[i-1][w], dp[i-1][w-weights[i]] + values[i]),其中weights[i]values[i]分别代表第i个物品的重量和价值。

最长公共子序列(LCS)

        最长公共子序列问题是寻找两个序列共有的最长子序列的长度。这个问题同样可以用动态规划来解决。我们定义一个二维数组dp[i][j],其中ij分别表示第一个和第二个序列的索引。dp[i][j]的值表示以ij结尾的最长公共子序列的长度。状态转移方程为:如果两个序列的当前元素相等,则dp[i][j] = dp[i-1][j-1] + 1;如果不等,则dp[i][j] = max(dp[i-1][j], dp[i][j-1])

其他经典问题

        除了背包问题和最长公共子序列问题,还有很多其他经典的动态规划问题,如编辑距离、最大子序和、硬币兑换问题等。每个问题都有其独特的问题设定和解决方案,但它们的解决思路都遵循了动态规划的核心原则:定义状态、设置边界条件、找到状态转移方程。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

昊昊该干饭了

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值