递归的四条基本法则:
(1)基准情形。 必须总有某些基准情形不用递归就能求解(递归的出口,通常将原问题不断划分到较小规模后,很容易获得解)。
(2)不断推进。 对于那些需要递归求解的情形,递归调用必须总能够朝着基准情形的方向推进。
(3)设计法则。假设所有的递归调用都能运行,这句话意味着当设计递归程序的时候一般没有必要知道管理细节,不必试图追踪大量的递归调用。追踪实际的递归调用常常是非常困难的,当然许多情形下,这正体现使用递归的好处,因为计算机能够算出复杂的细节。
(4)效益法则。在求解一个问题的同一实例时,切勿在不同的递归调用中做重复性的工作。例如使用递归计算斐波那契数之类的简单数学函数的值不是个好主意。
举一个例子:打印输出整数
设有一个正整数n,并希望把它打印出来。
分析:为打印76234,需要首先打印出7623,然后再打印4. 第二步用语句prinf(n % 10)很容易完成,但是第一步却不比原问题简单多少,他们实际上是同一问题,因此可以用prinfOut(n / 10)递归的解决它。
整个函数如下:
void printOut(int n)
{
if(n >= 10)
printOut(n / 10);
printf("%d", n % 10);
}
下面用数学归纳法对上述数字打印程序进行较为严格的证明
首先,如果n只有一位数字,那么程序显然是正确的。然后,假设printOut对所有K位或位数更少的数均能正常工作。k + 1位的数可以通过其前K位数字后跟一位最低位数字来表示。但前K位数字形成的数恰好是n / 10,由归纳假设它能够被正确打印出来,而最后一位数字是 n % 10,因此该程序能够正确打印出任意K + 1位的数。于是根据数学归纳法,所有数都能被正确打印出来。
证明阐述的是在设计递归程序时,同一问题的所有较小实例均可以假设运行正确,递归程序只需要把这些较小问题的解(他们通过递归奇迹般的得到)结合起来而形成当前问题的解。