递归算法就是经过解决同一问题的一个或多个更小的实例来最终解决一个大问题的算法。为了在C语言中实现递归算法,经常使用递归函数,也就是说能调用自身的函数。递归程序的基本特征:它调用自身(参数的值更小),具备终止条件,能够直接计算其结果。html
在使用递归程序时,咱们须要考虑编程环境必须可以保持一个其大小与递归深度成正比例的下推栈。对于大型问题,这个栈须要的空间可能妨碍咱们使用递归的方法。算法
一个递归模型为分治法,最本质的特征就是:把一个问题分解成独立的子问题。若是子问题并不独立,问题就会复杂的多,主要缘由是即便是这种最简单算法的直接递归实现,也可能须要不可思议的时间,使用动态规划技术就能够避免这个缺陷。编程
例如,斐波那契数列的递归实现以下:数组
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];
这个技术(dp)给了咱们一个获取任何递归关系数值解的快速方法。在斐波那契数的例子中,咱们甚至能够舍弃数组,只须要保存前两个值,这种思想有时能大幅代码,例如HDU-1003的第二种代码。工具
由上面的讨论咱们能够得出这样的结论:咱们能够按照从最小开始的顺序计算全部函数值来求任何相似函数的值,在每一步使用先前已经计算出的值来计算当前值,咱们称这项技术为自底向上的动态规划。只要有存储已经计算出的值的空间,就能把这项技术应用到任何递归计算中,就能把算法从指数级运行时间向线性运行时间改进。spa
自顶向下的动态规划甚至是一个更简单的技术,这项技术容许咱们执行函数的代价与自底向上的动态规划同样(或更小),可是它的计算是自动的。咱们实现递归程序来存储它所计算的每个值(正如它最末的步骤),并经过检查所存储的值,来避免从新计算它们的任何项(正如它最初的步骤)。这种方法有时也称做为备忘录法(记忆化搜索)。设计
斐波那契数--dp+记忆化搜索htm
经过把所计算的值存储在递归过程的外部数组中,明确地避免重复计算。这一程序计算的时间与N成正比。blog
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 咱们可能不须要计算全部子问题的解。
咱们不能忽视相当重要的一点是,当咱们须要的可能的函数值的数目太大以致于不能存储(自顶向下)或预先计算(自底向上)全部值时,动态规划就会变得低效。自顶向下动态规划确实是开发高效的递归算法实现的基本技术,这类算法应归入任何从事算法设计与实现所需的工具箱。