递归是一个强大的工具,用递归写的程序往往比较容易理解和实现。但是当面对一些递归性问题的时候,我们的第一感觉就是用递归程序实现,但是从问题到最终的实现程序之间需要经过什么?怎样才能写出正确的递归程序?我们将在这里进行关于递归的讨论。
我们首先介绍两个简单的递归实现程序,然后讨论循环与递归的关系,再结合之前递归程序,讨论如何才能写出正确的递归程序。
一、两个简单的递归程序
这里我们讨论阶乘的计算和斐波那契数列的计算。
首先我们给出这两个的非递归实现:
// 阶乘与斐波那契数列的非递归实现 #include <iostream> using namespace std; int fact(int n) { if (n == 0) { return 1; } int ret = 1; for (int i = 1; i != n+1; ++i) { ret *= i; } return ret; } int fibo(int n) { if (n == 0 || n == 1) { return n; } int a = 0, b = 1; int ret = 0; for (int i = 2; i != n+1; ++i) { ret = a + b; a = b; b = ret; } return ret; } int main() { cout << fact(5) << endl; cout << fibo(5) << endl; system("PAUSE"); return 0; }
以上关于阶乘与斐波那契数列的非递归实现分别用了循环实现。
下面我们讨论阶乘与斐波那契数列的递归实现。
阶乘的递归定义为:
fact(n) = n * fact(n-1)
当n = 0或1时,fact(0) = fact(1) = 1
斐波那契数列的递归定义为:
fibo(n) = fibo(n-1) + fibo(n-2)
当n=0时,fibo(0) = 0; 当n=1时,fibo(1) = 1
根据阶乘和斐波那契数列的递归定义,我们给出其递归程序实现:
// 阶乘和斐波那契数列的递归实现 #include <iostream> using namespace std; int fact(int n) { if (n == 0 || n == 1) { return 1; } return n * fact(n-1); } int fibo(int n) { if (n == 0 || n == 1) { return n; } return fibo(n-1) + fibo(n-2); } int main() { cout << fact(5) << endl; cout << fibo(5) << endl; system("PAUSE"); return 0; }
一个问题能够用递归实现需要满足两个条件:
1)、存在递归关系,这是递归的先决条件
2)、存在终止条件,否则程序无法终止
二、递归与循环的关系
从前面可以看出,阶乘和斐波那契数列的非递归实现分别包含了一个循环;而其递归实现里并没有循环结构,而仅是对其自身的递归调用。递归调用从某种意义上说相当于一个循环。一个递归调用,相当于一个循环。
三、递归的进一步讨论
前面我们有几篇是涉及递归实现的问题:
《如何生成升序序列》
从三组0-9中依次选择一个数字,并保持3个数字是非降序的
从三组0-9中依次选择一个数字,不要求这3个数字的升降序
生成集合的组合序列和排列序列
1)、如何生成升序序列
定义:A(N, >X) = M * A(N-1, >X+1),当N=0时,终止
递归实现:1、一个循环;2、一个递归调用
2)、从每组中依次选择一个元素
定义:A(N) = M * A(N-1),当N=0时,终止
递归实现:1、一个循环;2、一个递归调用
3)、组合序列、排列序列的生成实现
组合序列
定义:C(N, M) = C(N-1, M-1) + C(N-1, M),当M=0时,终止
实现:1、无循环;2、两个递归调用
排列序列
定义:A(N) = N * A(N-1),当N=0时,终止
实现:1、一个循环;2、一个递归调用
前面我们讨论了递归与循环的关系,一个递归调用相当于一个循环。在解决某种问题的递归实现时,如何判定是否有循环?
这里有两种方式,第一种是看递归定义,如果递归定义中只有自身的,则递归实现中没有循环,只有递归调用。如果递归定义中除了自身,还有其他变量,则递归实现中有对应于变量的循环以及关于自身的递归调用。
第二种方法是看问题原型,如果原型中只有一个循环,那么递归实现中只需要一个递归调用,不需要循环结构。如果原型中有两个循环,那么递归实现种除了一个递归调用外,还需要一个循环结构。
四、如何才能写出正确的递归程序
如果一个问题符合递归定义,那么就可以用递归方式实现。用递归程序解决问题的步骤归纳如下:
1)、首先明确问题的描述
2)、给出递归定义和终止条件
3)、根据递归定义决定递归调用和循环结构
4)、设计递归函数接口
5)、实现、测试
五、总结
这里我们讨论了关于递归程序如何书写,主要从问题的递归定义出发,看其定义是否包括变量和自身,再决定递归函数的定义结构即除了递归调用外是否还包含循环结构。
最后,我们归纳了如何才能写出正确的递归程序,主要分为5个步骤进行。在以后的工作中,如果碰到需要递归才能解决的问题,我们的第一出发点就是要给出其递归定义(而不是直接code)。先思考,再编程。