分析python动态规划的递归、非递归实现

针对“分析Python动态规划的递归、非递归实现”这个主题,我将分为以下几个部分进行完整的讲解。

针对“分析Python动态规划的递归、非递归实现”这个主题,我将分为以下几个部分进行完整的讲解。

1. 什么是动态规划

动态规划(Dynamic Programming)是一种通过把原问题分解为相对简单的子问题的方式,以递推的方式求解复杂问题的技术。在动态规划中,我们通常会用到“备忘录”或“DP表”来记录以前求解过的值,从而避免重复计算,提高程序效率。

动态规划的应用场景十分广泛,比如求最长公共子序列、背包问题、图像压缩等等。在刷LeetCode等编程题目的时候,也经常会用到。

2. 动态规划的递归实现

下面我以求解斐波那契数列为例,来详细讲解动态规划的递归实现。

2.1 斐波那契数列的定义

斐波那契数列是一个数学上比较经典的数列,它的定义如下:

F[0] = 0

F[1] = 1

F[i] = F[i-1] + F[i-2],(i>=2, i∈N*)

2.2 递归实现

先来看一下斐波那契数列的递归实现:

def fib(n):
    # base case
    if n == 0 or n == 1:
        return n
    # recursive case
    return fib(n-1) + fib(n-2)

在这个递归实现中,我们首先判断了n是否为0或1,如果是的话就直接返回n,否则就递归调用fib(n-1)和fib(n-2),并将它们的和返回。

递归实现的优点在于代码简洁易懂,但它的缺点也很明显,那就是会进行大量的重复计算。比如计算fib(5)的时候,fib(4)和fib(3)都会被计算一次,而计算fib(4)的时候,fib(3)还会被计算一次,这样就会浪费很多时间,效率较低。

3. 动态规划的非递归实现

了解了动态规划的递归实现之后,我们接下来来看一下非递归实现。

3.1 状态存储

使用动态规划进行求解时,我们通常需要定义一些状态,并将它们保存下来。在斐波那契数列的求解中,我们可以定义一个长度为n+1的数组dp来存储每一项的值,其中dp[i]就表示第i个斐波那契数列的值。

3.2 非递归实现

下面是斐波那契数列的非递归实现:

def fib(n):
    # base case
    if n == 0 or n == 1:
        return n

    dp = [0] * (n+1)
    dp[0], dp[1] = 0, 1

    for i in range(2, n+1):
        dp[i] = dp[i-1] + dp[i-2]

    return dp[n]

在这个非递归实现中,我们首先判断n是否为0或1,如果是的话就直接返回n。然后我们定义一个数组dp,并将第0个和第1个元素的值分别赋为0和1。接下来我们通过循环从2开始计算每个斐波那契数列的值,通过dp[i-1]和dp[i-2]的值相加来得到dp[i]的值。最后返回dp[n]即可。

4. 示例说明

在实际编程中,我们常常需要使用动态规划来解决问题。下面通过两个例子来加深对动态规划的理解。

4.1 求解最长递增子序列

假设我们有一个长度为n的序列a,请问它的最长递增子序列(LIS)是多长?

我们可以通过动态规划来解决这个问题。我们定义数组dp,其中dp[i]表示以第i个数结尾的最长递增子序列的长度。那么dp[i]的值怎么求呢?

当a[j] < a[i]时,我们可以将dp[i]设置为dp[j] + 1,因为此时可以将a[j]加入到以a[i]为结尾的最长递增子序列中。而当a[j] >= a[i]时,我们就不能将a[j]加入到以a[i]为结尾的最长递增子序列中了,此时dp[i]的值就不变。

最后,我们只需要遍历一遍a数组,求出所有的dp[i],再取其中的最大值,就是最长递增子序列的长度。

下面是求最长递增子序列的Python代码:

def lengthOfLIS(nums):
    n = len(nums)
    if n == 0:
        return 0
    dp = [1] * n
    for i in range(1, n):
        for j in range(i):
            if nums[j] < nums[i]:
                dp[i] = max(dp[i], dp[j] + 1)
    return max(dp)

4.2 求解最大子序和

给定一个长度为n的整数序列a,请你找出其中的一个连续子序列,使得它的和最大。其中,序列元素不允许为空。

我们可以通过动态规划来解决这个问题。我们定义数组dp,其中dp[i]表示以第i个数结尾的最大子序和。那么dp[i]的值怎么求呢?

当dp[i-1] > 0时,dp[i] = dp[i-1] + a[i],表示以a[i]为结尾的连续子序列一定包含a[i-1],因此可以将a[i]添加进去。而当dp[i-1] <= 0时,dp[i] = a[i],表示以a[i]为结尾的连续子序列只包含a[i]本身。

最后,我们只需要遍历一遍a数组,求出所有的dp[i],再取其中的最大值,就是最大子序和。

下面是求解最大子序和的Python代码:

def maxSubArray(nums):
    n = len(nums)
    dp = nums.copy()
    for i in range(1, n):
        dp[i] = max(dp[i], dp[i-1] + nums[i])
    return max(dp)

5. 总结

本文从动态规划的定义入手,详细讲解了动态规划的递归实现和非递归实现,并通过两个示例说明加深了对动态规划的理解。动态规划是一种非常有用的算法,掌握了它可以让我们在实际编程中事半功倍。

本文标题为:分析python动态规划的递归、非递归实现

基础教程推荐