递归和动态规划

递归算法就是通过解决同一问题的一个或多个更小的实例来最终解决一个大问题的算法。为了在C语言中实现递归算法,常常使用递归函数,也就是说能调用自身的函数。递归程序的基本特征:它调用自身(参数的值更小),具有终止条件,可以直接计算其结果。

      在使用递归程序时,我们需要考虑编程环境必须能够保持一个其大小与递归深度成正比例的下推栈。对于大型问题,这个栈需要的空间可能妨碍我们使用递归的方法。

     一个递归模型为分治法,最本质的特征就是:把一个问题分解成独立的子问题。如果子问题并不独立,问题就会复杂的多,主要原因是即使是这种最简单算法的直接递归实现,也可能需要难以想象的时间,使用动态规划技术就可以避免这个缺陷。

     例如,斐波那契数列的递归实现如下:

    int F(int i)

    {

             if(i < 1)  return 0;

             if(i == 1) return 1;

              return F(i-1) + F(i - 2);

    }

    千万不要使用这样的程序,因为它的效率极低,需要指数级时间。相比之下,如果首先计算前N个斐波那契数,并把它们存储在一个数组中,就可以使用线性时间(与N成正比)计算F。

 

      F[0] = 0;F[1] = 1;

      for(i = 2; i <= N; i++)

            F[i] = F[i-1] + F[i-2];

     这个技术给了我们一个获取任何递归关系数值解的快速方法,在斐波那契数的例子中,我们甚至可以舍弃数组,只需要保存前两个值。

     由上面的讨论我们可以得出这样的结论:我们可以按照从最小开始的顺序计算所有函数值来求任何类似函数的值,在每一步使用先前已经计算出的值来计算当前值,我们称这项技术为自底向上的动态规划。只要有存储已经计算出的值的空间,就能把这项技术应用到任何递归计算中,就能把算法从指数级运行时间向线性运行时间改进。

    自顶向下的动态规划甚至是一个更简单的技术,这项技术允许我们执行函数的代价与自底向上的动态规划一样(或更小),但是它的计算是自动的。我们实现递归程序来存储它所计算的每一个值(正如它最末的步骤),并通过检查所存储的值,来避免重新计算它们的任何项(正如它最初的步骤)。这种方法有时也称作为备忘录法。

                       斐波那契数(动态规划)

通过把所计算的值存储在递归过程的外部数组中,明确地避免重复计算。这一程序计算的时间与N成正比。

                  int F(int i)

                  {

 

                          if(knownF[i] != unknown)

                                 return knownF[i];

                          if(i == 0) t = 0;

                          if(i == 1) t = 1;

                          if(i > 1)  t = F(i - 1) + F(i - 2);

                          return knownF[i] = t;

                  }

 

 

       性质:动态规划降低了递归函数的运行时间,也就是减少了计算所有小于或等于给定参数的递归调用所要求的时间,其中处理一次递归调用的时间为常量。

       我们不需要把递归参数限制到单整形参数的情况。当有一个带有多个整形参数的函数时,可以把较小子问题的解存储在多维数组中,一个参数对应数组的一维。其他那些完全不涉及整形参数的情形,就使用抽象的离散问题公式,它能让我们把问题分解为一个个的小问题。

      在自顶向下的动态规划中,我们存储已知的值;在自底向上的动态规划中,我们预先计算这些值。我们常常选择自顶向下的动态规划而不选自底向上动态规划,其原因如下:

     1 自顶向下的动态规划是一个自然的求解问题的机械转化。

     2 计算子问题的顺序能自己处理。

     3 我们可能不需要计算所有子问题的解。

     我们不能忽视至关重要的一点是,当我们需要的可能的函数值的数目太大以至于不能存储(自顶向下)或预先计算(自底向上)所有值时,动态规划就会变得低效。自顶向下动态规划确实是开发高效的递归算法实现的基本技术,这类算法应纳入任何从事算法设计与实现所需的工具箱。

### 递归动态规划的主要差异 #### 定义与特性 递归是一种编程方法,在这种方法中,函数通过调用自身来解决问题。这种技术特别适用于那些可以通过分解成较小的相同问题来解决的情况[^2]。 相比之下,动态规划(Dynamic Programming, DP)则专注于优化重叠子问题的解决方案存储机制。当一个问题可以被划分为多个相互依赖的小型子问题时,动态规划能够有效地减少计算量并提升效率。特别是对于存在大量重复计算的情形下,动态规划的优势尤为明显[^3]。 #### 子问题处理方式 在递归过程中,每次遇到一个新的子问题都会触发新的递归调用;然而这可能导致大量的冗余运算,因为相同的子问题可能会被多次独立求解。例如,在斐波那契序列计算中,不加记忆化的简单递归会引发指数级增长的时间开销。 相反地,动态规划采用自底向上的策略构建解答路径——即从小到大地逐步累积已知的结果直至最终目标达成。这样做不仅避免了不必要的重复工作,而且使得整个过程更加高效有序。 #### 应用场景比较 - **适合使用递归的情景** - 当前问题是天然具备层次结构或树形分支特征的任务; - 需要清晰表达逻辑关系而不必过分关注性能瓶颈的应用场合。 - **更适合应用动态规划的例子** - 对于具有显著重叠性质的最优化类题目; - 寻找全局最优解而非局部极值的问题领域; - 数据集较大且涉及频繁访问历史状态更新当前决策的过程[^1]。 ```python def fibonacci_recursive(n): if n <= 1: return n else: return fibonacci_recursive(n-1) + fibonacci_recursive(n-2) def fibonacci_dp(n): fib = [0]*(n+1) fib[1] = 1 for i in range(2,n+1): fib[i]=fib[i-1]+fib[i-2] return fib[n] ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值