天天看点

《算法基础:打开算法之门》一2.4 递归

本节书摘来自华章出版社《算法基础:打开算法之门》一书中的第2章,第2.4节,作者 [美]托马斯 h 科尔曼(thomas h cormen),更多章节内容可以访问云栖社区“华章计算机”公众号查看

利用递归技术,能将一个问题转化为同一个样子问题的求解过程。这是我最喜欢的经典的递归例子:计算n!(“n的阶乘”),它被定义为如下,对于一个非负数n,当n=0时,n!=1且n!=n(n-1)(n-2)(n-3)…3•2•1(如果n≥1)。例如,5!=5•4•3•2•1=120。22观察得(n-1)!=(n-1)(n-2)(n-3)…3•2•1,并且得n!=n•(n-1)!(对于n≥1)。针对n!这个问题,我们定义了“较小的”问题,也就是(n-1)!。我们能够写出一个递归程序来计算n!,具体如下:

《算法基础:打开算法之门》一2.4 递归
《算法基础:打开算法之门》一2.4 递归

第2步的表达方式过于啰嗦。我可以将其改写为“otherwise,return n•factorial(n-1)”(否则,返回nfactor2al(n-1)),即在一个更大的算术表达式中使用递归调用的返回值。

对于递归,我们必须强调两个特性。首先,必须有一个或多个基础情况(base case),它是指不用递归而直接计算出结果。第二,程序中的每个递归调用一定是通过一系列关于同一问题的子问题的求解而最终迭代到基础情况。对于factorial程序,当n等于0时,基础情况发生,并且每一个递归调用最终均会将n归约到1。只要初始时n是非负的,递归调用最终均会归约到基础情况。

证明递归算法工作流程的第一步可能让人感觉过于简单。证明的关键是每次递归调用均会产生正确的结果。只要我们愿意相信递归调用确实得到了正确结果,那么证明正确性通常是容易的。如下是我们如何证明factorial程序返回了正确的结果。很明显,当n=0时,结果返回1,1等价于n!。假定当n≥1时,factorial(n-1)这个递归调用返回了正确的结果:它返回了(n-1)!。该程序再用n乘以该值,因此计算出了n!这一结果,也就是最后要返回的值。

下面举一个程序,虽然它利用了正确的数学公式计算,但它不是基于子问题求解的递归调用。当n≥0时,确实有n!=(n+1)!/(n+1)。下列递归程序虽然利用了该数学公式,但是未能得出正确的结果(当n≥1时):

《算法基础:打开算法之门》一2.4 递归

如果要调用badfactorial(1),那么它会产生一个递归调用badfactorial(2),该递归调用会产生一个递归调用badfactorial(3),等等,这一系列递归调用均不会归约到基础情况(即n=0事件)。如果要用一种实际编程语言来实现该程序并且将该程序运行到一个真正的计算机上,那么你会很快就能看到一条“栈溢出错误”的错误信息。我们通常能将算法表示成一种递归风格的循环方式。如下是一个线性查找算法,它没有使用标记,而是使用递归方式书写的:

《算法基础:打开算法之门》一2.4 递归

这里,子问题是在a[i]~a[n]这个子数组中寻找x的问题。基础情况发生在第1步中即当子数组本身是空时,也就是当i>n时。因为每执行一次第3步的递归调用,如果没有第2步中的值返回,i的值就会自增一,那么最后i会大于n,此时我们会执行基础情况。

继续阅读