动态规划(二)

本文深入探讨动态规划的两大关键特性:最优子结构和重叠子问题,阐述如何利用这两个特性解决最优化问题。文章通过最长公共子序列问题为例,详细解释动态规划的应用和解决方案,包括递归解法和构造最优解的过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

动态规划原理

    之前的两个问题都是用动态规划方法解决的,那么什么情况下需要使用动态规划呢?适应动态规划方法求解的最优化问题应该具备的两个要素:最优子结构和子问题重叠。

最优子结构

    用动态规划方法求解最优化问题的第一步就是刻画最优解的结构。如果一个问题的最优解包含其子问题的最优解,就称此问题具有最优子结构性质。使用动态规划方法时,我们用子问题的最优解来构造原问题的最优解,因此就要仔细考察最优解中用到的所有子问题。
    在发掘最优子结构性质的过程中,遵循了如下的通用模式:
1. 证明问题最优解的第一个组成部分是做出一个选择,做出这次选择会产生一个或多个待解的子问题。
2. 对于一个给定问题,在其可能的第一步选择中,假定已经知道哪种选择才会得到最优解,并不关心这种选择具体是如何得到的,只是假定已经知道了这种选择。
3. 给定可获得最优解的选择后,确定这次选择会产生哪些子问题,以及如何最好地刻画子问题空间。
4. 证明作为构成原问题最优解的组成部分,每个子问题的解就是它本身的最优解,可利用反证法证明。
    刻画子问题空间的经验是:保持子问题空间尽可能简单,只在必要时才扩展它。
    对于不同问题领域,最优子结构体的不同体现在两个方面:
- 原问题的最优解中涉及多少个子问题,以及
- 在确定最优解使用那些子问题时,我们需要考察多少种选择。
    在动态规划方法种,通常自底向上的使用最优子结构,也就是说,首先求得子问题的最优解,然后求原问题的最优解,在求解原问题过程中,需要在涉及的子问题中做出选择,选择得到原问题最优解的子问题,原问题最优解的代价通常就是子问题最优解的代价加上由此次选择直接产生的代价。
    在尝试使用动态规划方法时要小心,要注意问题是否具有最优子结构性质。对于一个无权有向图来说,无权最短路径自然可以使用动态规划方法进行求解,但无权最长简单路径就不适用动态规划方法了,原因是求解一个子问题时用到了某些资源,导致这些资源在求解其他子问题时不可用。

重叠子问题

    适合动态规划方法求解的最优化问题应该具备的第二个性质是子问题空间必须足够“小”,即问题的递归算法会反复求解相同的子问题,而不是一直生成新的子问题,一般来讲,不同子问题的总数是输入规模的多项式函数为好,如果递归算法反复求解相同的子问题,就称最优化问题具有重叠子问题性质,与之相对的,适合用分治方法求解的问题通常在递归的每一步都生成全新的子问题。
    凡是一个问题的自然递归算法的递归调用树中反复出现相同的子问题,而不同子问题的总数据很少时,动态规划方法都能提高效率。
    带备忘的递归算法为每个子问题维护一个表项来保存它的解,每个表项的初值设为一个特殊值,表示尚未填入子问题的解,当递归调用过程中第一次遇到子问题时,计算其解,并存入对应表项,随后每次遇到同一个子问题,只是简单的查询,返回其解。

最长公共子序列

问题描述

    最长公共子序列问题给定两个序列X=(x1,x2,•••,xm)和Y=(y1,y2,•••,yn),求X和Y长度最长的公共子序列。

最长公共子序列的特征

    最优子结构:令X=(x1,x2,•••,xm)和Y=(y1,y2,•••,yn)为两个序列,Z=(z1,z2,•••,zk)为X和Y的任意最长公共子序列(LCS)。
- 如果xm=yn,则zk=xm=yn且Z(k-1)是X(m-1)和Y(n-1)的一个LCS。
- 如果xm≠yn,那么zk≠xm意味着Z是X(m-1)和Y的一个LCS。
- 如果xm≠yn,那么zk≠yn意味着Z是X和Y(n-1)的一个LCS。

一个递归解

    定义c[i, j]表示Xi和Yj的LCS的长度,c[i, j]的取值可以由下面公式表示:
0, (i=0或j=0)
c[i - 1, j - 1] + 1, (i,j > 0且xi=yj)
max(c[i, j - 1], c[i – 1, j]), (i,j > 0且xi≠yi)

计算最优解的值

    通过辅助表b[i, j]来构造最优解,b[i, j]指向的表项对应计算c[i, j]时所选择的子问题最优解。伪代码如下:

LCS_LENGTH(X, Y)
m = X.length
n = Y.length
let b[m + 1, n + 1] and  c[m + 1, n + 1] be new array
for i = 1 to m
  c[i, 0] = 0
for j = 1 to n
  c[0, j] = 0
for i = 0 to m
  for j = 1 to n
    if x[i] == y[i]
      c[i, j] = c[i – 1, j – 1] + 1
      b[i, j] = “↖”
    else if c[i – 1, j] >= c[i, j – 1]
      c[i, j] = c[i – 1, j]
      b[i, j] = “↑”
    else
      c[i, j] = c[i, j – 1]
      b[i, j] = “←”
return c and b

构造最优解

    利用b[i, j]中的箭头进行追踪打印,实现的伪代码如下:

PRINT_LCS(b, X, i, j)
if i == 0 or j == 0
  return
if b[i, j] == “↖”
  PRINT_LCS(b, X, i - 1, j - 1)
  print x[i]
else if b[i, j] == “↑”
  PRINT_LCS(b, X, i - 1, j)
else
  PRINT_LCS(b, X, i, j - 1)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值