算法分析之动态规划三

       前两篇是在上算法课的时候对动态规划的初步了解,今面临找工作的需求,又把动态规划复习了一下,在理解上比之前更深了一点点,之前太偏向于解决单个问题。例如 0-1背包问题,现在站在程序设计与优化的角度结合两篇不错的博客来做个简单的整理。

本篇重在分析动态规划与递归的关系

动态规划分两种形式:一种是自顶向下的,采用递归;另一种是自底向上的,从求解最小的子问题开始,逐步求解得原问题,就是递推法。你可以参考《算法导论》,里面讲得比较细致

在程序设计中递归是个很有用的工具,可以毫不夸张地说递归式 分治算法和动态规划的基石。

      递归算法就是通过解决同一问题的一个或多个更小的实例来最终解决一个大问题的算法。为了在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。

二:采用循环向后计算

     注意:这是个递推式子,不是个递归的式子!

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

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

三:自低向上的动态规划

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

                       斐波那契数(动态规划)

剑指offer提供的替代方案仍然具有一定的技巧性,所谓技巧性也即不可复制性,这里提供一种通用的解决方案,所谓的通用,是因为它利用了一种算法思想:动态规划(DP:dynamic programming),这里抛开动态规划严格且抽象的定义,我们从它的实现方式入手,也即记录结果再利用,我们来看动态规划版本的计算方法:

long long memo[N+1];        // 全局变量,初始化为0
long long fib(unsigned n)
{
    if (n <= 1)
        return n;
    if (memo[n]) return memo[n];
    else
        return memo[n] = fib(n-1) + fib(n-2);
}

       这里每次return memo[n] 给调用它的函数,减少了右侧从重复的额计算!

       这里看似好像仍然是之前的递归形式相同,然而,通过查看其递归调用的流程可以发现,分支的右侧,已在左侧计算出且保存在mem[n]

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

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

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

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

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

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

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值