首页 > 其他分享 >动态规划——问题的特征与求解步骤精解

动态规划——问题的特征与求解步骤精解

时间:2024-09-21 22:25:19浏览次数:7  
标签:动态 求解 步骤 路径 问题 path 最优 精解 dp

动态规划算法是通过拆分问题,定义问题状态和状态之间的关系,使得问题能够以递推的方式去解决。动态规划算法的基本思想与分治法类似,也是将待求解的问题分解为若干个子阶段,按顺序求解子阶段。与分治不同的是,在动态规划过程中,前一子问题的解,为后一子问题的求解提供了有用的信息,也就是说前后阶段的问题求解存在依赖关系。在求解任一子问题时,通过决策保留那些有可能达到最优的局部解。依次解决各子问题,最后一个子问题就是初始问题的解。

一、动态规划问题的特征

最优化原理(最优子结构): 最优子结构是动态规划能够应用的基础。最优子结构意味着一个问题的最优解可以通过其子问题的最优解来构造。如果问题的最优解所包含的子问题的解也是最优的,就称该问题具有最优子结构。换句话说,当前问题的最优解可以通过对较小子问题的最优解进行组合而得出。
例子:在背包问题中,背包总容量为 \(W\),物品集合为 \(n\) 个。我们可以将这个问题划分为两个子问题:是否选择某个物品 \(i\)。如果选择该物品,问题就转化为容量为 \(W - w_i\) 的最优解;如果不选择,问题则变为容量为 \(W\) 时剩下 \(n - 1\) 个物品的最优解。两个子问题的最优解决定了整体问题的最优解。

无后效性: 无后效性指的是某阶段的状态一旦确定,就不受这个状态以后决策的影响。这意味着某个状态所涉及的决策只依赖于当前的状态和前面的决策,而不会受到将来决策的影响。通俗地讲,一旦状态 \(x_k\) 被选定,那么系统从当前阶段 \(k\) 开始的后续行为只与 \(x_k\) 相关,与此前的决策无关。
例子:在路径规划问题中,选择到达某一个点 \(A\) 的最短路径不需要考虑之前是通过哪条路径到达的,只需要知道从点 \(A\) 到终点的距离以及经过 \(A\) 的最短路径即可。因此,当前阶段的决策只取决于当前状态,而不会依赖之前如何走到这个状态。

重叠子问题: 重叠子问题指的是在递归求解的过程中,不同的阶段可能会遇到相同的子问题。为了避免重复计算,可以将这些子问题的结果保存起来,以供后续使用,从而降低算法复杂度。相比于贪心算法或分治法,动态规划的优势在于重叠子问题的存在。
例子:在斐波那契数列求解问题中,计算 \(F(n)\) 时需要计算 \(F(n-1)\) 和 \(F(n-2)\),但是在计算 \(F(n-1)\) 时又会再次计算 \(F(n-2)\),这就是重叠子问题。如果不进行保存,计算过程中会出现大量的重复计算。通过动态规划保存这些中间结果,可以极大地提高效率。

二、动态规划求解的步骤

划分阶段: 将问题按照一定的顺序划分为若干个阶段,通常每个阶段表示某种子问题。划分阶段的原则是确保子问题之间可以递推,并且后续的决策只依赖当前的状态。
选择状态变量: 选择合适的状态变量来描述每个阶段的状态。状态变量要尽量全面地描述当前的情况,并确保能够通过状态转移方程递推。
建立状态转移方程: 确定状态之间的递推关系,即状态转移方程。状态转移方程是动态规划的核心,明确了如何从一个状态过渡到另一个状态。
定义边界条件: 为了递推求解,必须定义初始状态的解,通常是最小阶段的解。边界条件为动态规划的起始点,递推从这里开始。
自底向上求解: 动态规划采用自底向上的方式求解,即从最小的子问题开始递推,逐步解决更大的问题。通过存储子问题的解,避免重复计算,提高效率。
输出最优解: 最终,动态规划输出的是最优值函数的值,即问题的最优解。

三、动态规划问题求解

3.1 青蛙跳阶

一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 10 级的台阶总共有多少种跳法。

假设青蛙跳上第\(n\)级台阶,有两种方式:从第\(n-1\)级跳上来;从第\(n-2\)级跳上来。因此,要跳上第\(n\)级台阶的总跳法数等于跳上第\(n-1\)级台阶的跳法数加上跳上第\(n-2\)级台阶的跳法数。动态规划建模过程如下:

  • 阶段(Stage):每一阶段对应跳上某一级台阶。共有\(n\)阶台阶,从 1 到\(n\)阶。
  • 状态(State):状态可以用\(f(n)\)表示跳上第\(n\)级台阶的总跳法数。
  • 状态转移方程(State Transition Equation):如上分析,状态转移方程为:$$f(n) = f(n-1) + f(n-2)$$
  • 边界条件
    • 跳上第 1 级台阶时,只有一种跳法,即\(f(1) = 1\);
    • 跳上第 2 级台阶时,有两种跳法,即\(f(2) = 2\)(可以一次跳 1 级,也可以一次跳 2 级)。
  • 根据状态转移方程和边界条件,递推方程为:$$f(n) = f(n-1) + f(n-2), \quad \text{for } n \geq 3$$
def frog_jumps(n):
    # 边界条件
    if n == 1:
        return 1
    elif n == 2:
        return 2
    
    # 动态规划数组
    dp = [0] * (n + 1)
    dp[1] = 1  # 跳上第 1 级台阶只有 1 种方法
    dp[2] = 2  # 跳上第 2 级台阶有 2 种方法
    
    # 递推求解每一级台阶的跳法数
    for i in range(3, n + 1):
        dp[i] = dp[i - 1] + dp[i - 2]
    
    return dp[n]

# 求解青蛙跳上 10 级台阶的跳法总数
n = 10
result = frog_jumps(n)
print(f"跳上 {n} 级台阶的总跳法数为:{result}")

3.2 矩形最小路径和

矩形最小路径和问题是经典的动态规划问题。题目通常要求我们从一个矩形网格的左上角出发,经过某些路径到达右下角,使得经过的路径上的数值和最小。只能向下或者向右移动。假设一个\(4 \times 5\)的网格,其每个格子中的数值表示路径的权重,网格数据如下:
[1, 3, 1, 4, 2]
[1, 5, 1, 2, 3]
[4, 2, 1, 7, 2]
[1, 2, 3, 1, 1]

设 \(dp[i][j]\) 表示到达坐标 \((i, j)\) 的最小路径和。

状态转移方程

  • 若 \(i == 0\),则 \(dp[i][j] = dp[i][j-1] + grid[i][j]\)(只能从左边过来)
  • 若 \(j == 0\),则 \(dp[i][j] = dp[i-1][j] + grid[i][j]\)(只能从上面过来)
  • 否则,\(dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + grid[i][j]\)(可以从上方或左方过来,取较小者)

初始状态:\(dp[0][0] = grid[0][0]\)

目标:计算出 \(dp[3][4]\),即从左上角到右下角的最小路径和。

def minPathSumWithTrace(grid):
    rows = len(grid)
    cols = len(grid[0])
    
    # 创建 dp 数组和路径数组
    dp = [[0 for _ in range(cols)] for _ in range(rows)]
    path = [[None for _ in range(cols)] for _ in range(rows)]  # 用于记录路径

    # 初始化起点
    dp[0][0] = grid[0][0]
    path[0][0] = (-1, -1)  # 用于标记起点

    # 初始化第一行
    for j in range(1, cols):
        dp[0][j] = dp[0][j-1] + grid[0][j]
        path[0][j] = (0, j-1)  # 只能从左边过来

    # 初始化第一列
    for i in range(1, rows):
        dp[i][0] = dp[i-1][0] + grid[i][0]
        path[i][0] = (i-1, 0)  # 只能从上边过来

    # 填充 dp 数组
    for i in range(1, rows):
        for j in range(1, cols):
            # 比较从上方和左方的路径,选择最小的
            if dp[i-1][j] < dp[i][j-1]:
                dp[i][j] = dp[i-1][j] + grid[i][j]
                path[i][j] = (i-1, j)  # 记录从上方来的路径
            else:
                dp[i][j] = dp[i][j-1] + grid[i][j]
                path[i][j] = (i, j-1)  # 记录从左方来的路径

    # 回溯路径
    min_path = []
    i, j = rows - 1, cols - 1  # 从右下角开始
    while (i, j) != (-1, -1):  # 一直到起点
        min_path.append((i, j))
        i, j = path[i][j]
    
    # 返回最小路径和以及路径(逆序后输出路径)
    return dp[rows-1][cols-1], min_path[::-1]  # 逆序输出路径

# 测试数据
grid = [
    [1, 3, 1, 4, 2],
    [1, 5, 1, 2, 3],
    [4, 2, 1, 7, 2],
    [1, 2, 3, 1, 1]
]

# 调用函数,输出最小路径和和路径
result, min_path = minPathSumWithTrace(grid)
print("矩形最小路径和为:", result)
print("最小路径为:", min_path)

3.3 数字三角形的最大路径和

在数字三角形中寻找一条从顶部到底边的路径,使得路径上所经过的数字之和最大。路径上的每一步都只能往左下或右下走。只需要求出这个最大和即可,不必给出具体路径。 三角形的行数大于 1 小于等于 100,数字为 0 - 99。

如果当前在第 \(i\) 行的第 \(j\) 个元素,那么它的最大路径和可以通过下面的公式计算:$$f(i, j) = a(i, j) + \max(f(i+1, j), f(i+1, j+1))$$
其中:

  • \(f(i, j)\) 表示从位置 \((i, j)\) 开始到达底部的最大路径和。
  • \(a(i, j)\) 是当前的数字。

def max_path_sum_with_trace(triangle):
    # 行数
    n = len(triangle)
    # 初始化路径数组,记录每个点的最佳选择(0表示向左下,1表示向右下)
    path = [[-1] * len(row) for row in triangle]
    
    # 建立一个副本用于存储最大路径和,不改变原三角形的值
    dp = [row[:] for row in triangle]

    # 从倒数第二层开始向上计算最大路径和
    for i in range(n - 2, -1, -1):
        for j in range(len(dp[i])):
            # 动态规划方程,选择较大的那个方向
            if dp[i + 1][j] > dp[i + 1][j + 1]:
                dp[i][j] += dp[i + 1][j]
                path[i][j] = 0  # 选择左下
            else:
                dp[i][j] += dp[i + 1][j + 1]
                path[i][j] = 1  # 选择右下
    
    # 计算最大路径和
    max_sum = dp[0][0]
    
    # 追踪路径,记录经过的数字
    j = 0  # 从顶部开始
    optimal_path = [triangle[0][0]]  # 记录路径上的数字
    
    for i in range(1, n):
        if path[i - 1][j] == 0:
            j = j  # 左下
        else:
            j = j + 1  # 右下
        optimal_path.append(triangle[i][j])  # 将选择的数字加入路径

    return max_sum, optimal_path

# 示例三角形,行数为5
triangle = [
    [7],
    [3, 8],
    [8, 1, 0],
    [2, 7, 4, 4],
    [4, 5, 2, 6, 5]
]

# 调用函数
max_sum, path = max_path_sum_with_trace(triangle)
print("最大路径和:", max_sum)
print("经过的数字路径:", path)

总结

动态规划是一种用于解决具有最优子结构和重叠子问题的优化算法。其核心思想是通过将复杂问题分解为若干个相互关联的子问题,逐步求解并保存每个子问题的结果,避免重复计算,最终得到问题的最优解。动态规划最优子结构意味着问题的整体最优解可以通过子问题的最优解构成,确保递推求解的可行性;无后效性保证每个阶段的状态一旦确定,不受未来决策影响,只依赖当前的状态和之前的决策;重叠子问题则是动态规划效率的源泉,通过保存子问题的结果,可以避免多次计算同一子问题。动态规划的求解步骤包括:划分阶段、定义状态变量、构建状态转移方程、设定初始条件,并自底向上递推求解。常见应用领域包括背包问题、路径规划、股票买卖等。总的来说,动态规划是解决复杂优化问题的高效方法,尤其适用于具有递归结构的问题。

参考文献

  1. 五大算法思想(五)动态规划及常见例子
  2. 第9章 动态规划

标签:动态,求解,步骤,路径,问题,path,最优,精解,dp
From: https://www.cnblogs.com/haohai9309/p/18424384

相关文章

  • After Effects2024中文版下载:附安装包+详细安装步骤
    如大家所熟悉的,AfterEffects常常被简称为AE,它是一款专业图形视频处理软件,适用于从事设计和视频特效的机构和个人。在视频创作中熟练使用它,可以帮助您高效且精确地创建无数种引人注目的动态图形和震撼人心的视觉效果。相信用过Premiere(PR)这款视频剪辑工具的小伙伴,对AE更加不......
  • 11----mtk芯片专用解锁工具 解除FRP 很小的工具 去除屏幕锁 免授权等等 工具预览与步
         机型的FRP锁是谷歌账号锁。工具是mtk芯片使用。可以去除当前机型的FRP和米账号重置。操作非常简单。但前提是联机驱动要装好。任何的工具联机驱动是关键。工具功能选项★★★★★工具开发者说明功能与选项操作与资源下载★★★★★具体工具操作使用指南工......
  • 从数据中台到数据飞轮:实现企业数据驱动转型的关键步骤
    随着数字变革的加速,企业对数据的依赖日益增加。数据中台作为汇集、整合企业各类数据资源的平台,已被广泛认为是企业构建数据能力的重要基础。然而,仅建立数据中台并不能完全释放数据的潜力,关键在于如何利用这些数据推动业务增长和创新。这便引出了数据飞轮的概念,即通过不断的数据积累......
  • 存储论——报童问题(单周期)订货模型精解
    报童问题(NewsvendorProblem),最早由哈维·莫德里格利亚尼(HarveyM.Wagner)和托马斯·M·怀特(ThomasM.Whitin)于1958年提出,是运筹学中经典的库存管理问题。其名称源于报童的情境描述,即一个报童每天需要决定订购多少份报纸以最大化利润。报童每天面对报纸需求的不确定性,若订购量太少......
  • Linux系统离线部署MySQL详细教程(带每步骤图文教程)
    1、登录官网下载对应的安装包MySQL::DeveloperZone2、将压缩包上传到服务器上,这里直接上传到/usr/local路径上使用sftp工具上传到/usr/local目录上3、解压压缩包 tar-xfmysql-8.0.39-linux-glibc2.17-x86_64.tar.xz4、将mysql-8.0.39-linux-glibc2.17-x86......
  • 无人机集群路径规划:麻雀搜索算法(Sparrow Search Algorithm, SSA)​求解无人机集群路
     一、单个无人机路径规划模型介绍无人机三维路径规划是指在三维空间中为无人机规划一条合理的飞行路径,使其能够安全、高效地完成任务。路径规划是无人机自主飞行的关键技术之一,它可以通过算法和模型来确定无人机的航迹,以避开障碍物、优化飞行时间和节省能量消耗。二、无人......
  • 无人机集群路径规划:​北方苍鹰优化算法(Northern Goshawk Optimization,NGO)​求解无人机
     一、单个无人机路径规划模型介绍无人机三维路径规划是指在三维空间中为无人机规划一条合理的飞行路径,使其能够安全、高效地完成任务。路径规划是无人机自主飞行的关键技术之一,它可以通过算法和模型来确定无人机的航迹,以避开障碍物、优化飞行时间和节省能量消耗。二、无人......
  • 排队论——随机服务系统仿真精解
    排队论作为研究随机服务系统的重要工具,专门研究系统中客户到达、排队、服务和离开的过程。排队论的核心目的是通过数学建模和分析,研究系统的性能指标,如平均等待时间、队列长度、系统的吞吐量等。虽然排队论提供了强大的数学工具来分析随机服务系统,但在许多复杂的实际问题中,精确的......
  • 虚拟机(VMware)安装,保姆级教程(附所有安装包及所有安装步骤)
    1.安装包下载1.1VMware下载VMware安装包提取码:b9ds1.2镜像下载镜像安装包提取码:hbtq2.配置虚拟机向导2.1配置虚拟机向导2.2选择虚拟机硬件兼容性2.3安装客户机操作系统2.4简易安装信息2.5命名虚拟机2.6处理器配置#这个根据自己需求来定 有的服务定的低了......
  • 国家标准如何起草?有哪些步骤?
    国家标准的起草,是一项艰巨而又意义重大的任务,它关乎着国家的经济发展、社会稳定以及人民的生活质量。当我们肩负起起草国家标准的重任时,便如同踏上了一段充满挑战与机遇的征程。一、前期准备1.明确需求:确定制定国家标准的必要性和目的,了解相关行业的发展现状、技术趋势......