【轻松掌握数据结构与算法】动态规划

引言

在本章中,我们将尝试解决那些使用其他技术(例如分治法和贪心法)未能得到最优解的问题。动态规划(DP)是一种简单的技术,但掌握起来可能比较困难。识别和解决DP问题的一个简单方法就是尽可能多地解决各种问题。“编程”一词与编码无关,而是源自文献,意思是填充表格,类似于线性规划。

什么是动态规划策略?

动态规划和记忆化搜索是相辅相成的。分治法和动态规划的主要区别在于,分治法中的子问题是相互独立的,而在DP中子问题可能会重叠。通过使用记忆化搜索(维护一个已解决子问题的表格),动态规划将许多问题的指数级复杂度降低到多项式级复杂度(O(n²)、O(n³)等)。DP的主要组成部分包括:

  • 递归:递归地解决子问题。

  • 记忆化:将已计算的值存储在表格中(记忆化意味着缓存)。 动态规划 = 递归 + 记忆化

动态规划策略的特性

能够判断DP是否能解决给定问题的两个特性是:

  • 最优子结构:一个问题的最优解包含其子问题的最优解。

  • 重叠子问题:递归解中包含少量不同的子问题,这些子问题被重复多次。

动态规划能否解决所有问题?

和贪心法与分治法一样,DP并不能解决所有问题。有些问题无法通过任何算法技术(贪心法、分治法和动态规划)来解决。动态规划与直接递归的区别在于递归调用的记忆化。如果子问题是相互独立且没有重复,那么记忆化就没有帮助,因此动态规划并非所有问题的解决方案。

动态规划方法

解决DP问题主要有两种方法:

  • 自底向上动态规划

  • 自顶向下动态规划

自底向上动态规划

在这种方法中,我们从最小的可能输入参数值开始评估函数,然后逐步增加输入参数值。在计算过程中,我们将所有计算出的值存储在表格(内存)中。当评估较大参数值时,可以使用之前计算出的较小参数值。

示例:斐波那契数列

斐波那契数列中,当前数字是前两个数字的和。斐波那契数列定义如下: F(n)=F(n−1)+F(n−2) 其中,F(0)=0,F(1)=1。

观察斐波那契数列可以发现,当前值仅是前两个计算值的和。这意味着我们无需存储所有先前的值,只需存储最后两个值即可计算当前值。

def fib(n):
    if n <= 1:
        return n
    a, b = 0, 1
    for _ in range(2, n+1):
        a, b = b, a + b
    return b

这种实现的时间复杂度为O(n),空间复杂度为O(1)。

自顶向下动态规划

在这种方法中,我们将问题分解为子问题,解决每个子问题,并记住解决方案,以防需要再次解决。我们还将每个计算出的值作为递归函数的最后一个动作保存,并在第一个动作中检查是否存在预先计算的值。

自底向上与自顶向下编程对比

在自底向上编程中,程序员需要选择要计算的值并决定计算顺序。在这种情况下,所有可能需要的子问题都提前解决,然后用来构建更大问题的解决方案。在自顶向下编程中,原始代码的递归结构得以保留,但避免了不必要的重复计算。问题被分解为子问题,这些子问题被解决并记住,以防需要再次解决。

示例:阶乘问题

阶乘问题:n! 是 n 和 1 之间所有整数的乘积。递归阶乘的定义可以表示为: n!=n×(n−1)! 其中,0!=1。

我们可以使用动态规划来降低复杂度。从递归定义可以看出,fact(n) 是通过 fact(n-1) 和 n 计算得出的。我们可以将之前计算出的值存储在表格中,并使用这些值来计算新值。

def factorial(n, memo={}):
    if n in memo:
        return memo[n]
    if n == 0 or n == 1:
        return 1
    memo[n] = n * factorial(n-1, memo)
    return memo[n]

这种实现将复杂度降低到O(max(m,n))。

动态规划算法示例

  • 许多字符串算法,包括最长公共子序列、最长递增子序列、最长公共子串、编辑距离等。

  • 图算法可以高效解决:Bellman-Ford算法用于在图中查找最短距离,Floyd的全对最短路径算法等。

  • 链式矩阵乘法

  • 子集和问题

  • 0/1背包问题

  • 旅行商问题等

理解动态规划

在深入问题之前,让我们通过示例了解DP的工作原理。

1)斐波那契数列

斐波那契数列中,当前数字是前两个数字的和。斐波那契数列定义如下: F(n)=F(n−1)+F(n−2) 其中,F(0)=0,F(1)=1。

递归实现

def fib(n):
    if n <= 1:
        return n
    return fib(n-1) + fib(n-2)

这种递归实现的时间复杂度为指数级,因为它会重复计算许多子问题。

记忆化搜索

为了避免重复计算,我们可以使用记忆化搜索。具体做法是:从递归函数开始,添加一个表格,将函数

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值