在写算法题的时候肯定会遇到递归,有时候会觉得递归难以理解,在《Linux C编程一站式学习》中看到了对递归的详细拆解,记录一下。
以factorial(3)为例分析整个调用过程,如下图所示:
factorial(3)的调用过程
图中用实线箭头表示调用,用虚线箭头表示返回,右侧的框表示在调用和返回过程中各层函数调用的存储空间变化情况。
-
main()有一个局部变量result,用一个框表示。
-
调用factorial(3)时要分配参数和局部变量的存储空间,于是在main()的下面又多了一个框表示factorial(3)的参数和局部变量,其中n已初始化为3。
-
factorial(3)又调用factorial(2),又要分配factorial(2)的参数和局部变量,于是在main()和factorial(3)下面又多了一个框。第 4 节 “全局变量、局部变量和作用域”讲过,每次调用函数时分配参数和局部变量的存储空间,退出函数时释放它们的存储空间。factorial(3)和factorial(2)是两次不同的调用,factorial(3)的参数n和factorial(2)的参数n各有各的存储单元,虽然我们写代码时只写了一次参数n,但运行时却是两个不同的参数n。并且由于调用factorial(2)时factorial(3)还没退出,所以两个函数调用的参数n同时存在,所以在原来的基础上多画一个框。
我们看上图右侧存储空间的变化过程,随着函数调用的层层深入,存储空间的一端逐渐增长,然后随着函数调用的层层返回,存储空间的这一端又逐渐缩短,并且每次访问参数和局部变量时只能访问这一端的存储单元,而不能访问内部的存储单元,比如当factorial(2)的存储空间位于末端时,只能访问它的参数和局部变量,而不能访问factorial(3)和main()的参数和局部变量。具有这种性质的数据结构称为堆栈或栈(Stack),随着函数调用和返回而不断变化的这一端称为栈顶,每个函数调用的参数和局部变量的存储空间(上图的每个小方框)称为一个栈帧(Stack Frame)。操作系统为程序的运行预留了一块栈空间,函数调用时就在这个栈空间里分配栈帧,函数返回时就释放栈帧。
如果你相信你正在写的递归函数是正确的,并调用它,然后在此基础上写完这个递归函数,那么它就会是正确的,从而值得你相信它正确。
这么说好像有点儿玄,我们从数学上严格证明一下factorial函数的正确性。刚才说了,factorial(n)的正确性依赖于factorial(n-1)的正确性,只要后者正确,在后者的结果上乘个n返回这一步显然也没有疑问,那么我们的函数实现就是正确的。因此要证明factorial(n)的正确性就是要证明factorial(n-1)的正确性,同理,要证明factorial(n-1)的正确性就是要证明factorial(n-2)的正确性,依此类推下去,最后是:要证明factorial(1)的正确性就是要证明factorial(0)的正确性。而factorial(0)的正确性不依赖于别的函数调用,它就是程序中的一个小的分支return 1;,这个1是我们根据阶乘的定义写的,肯定是正确的,因此factorial(1)的实现是正确的,因此factorial(2)也正确,依此类推,最后factorial(n)也是正确的。其实这就是在中学时学的数学归纳法(Mathematical Induction),用数学归纳法来证明只需要证明两点:Base Case正确,递推关系正确。写递归函数时一定要记得写Base Case,否则即使递推关系正确,整个函数也不正确。
几个小练习:
/**
* n的阶乘等于n乘以n-1的阶乘。
* 0的阶乘等于1(Base Case)
* @param num
* @return
*/
private static int factorial(int num) {
if (num == 0) {
return 1;
} else {
return num * factorial(num -1);
}
}
/**
* 如果a除以b能整除,则最大公约数是b。(Base Case)
* 否则,最大公约数等于b和a%b的最大公约数。
* @param a
* @param b
* @return
*/
private static int euclidofgcd(int a, int b) {
if (a % b == 0) {
return b;
} else {
return euclidofgcd(b, a % b);
}
}
/**
* fib(0)=1 (Base Case)
* fib(1)=1 (Base Case)
* fib(n)=fib(n-1)+fib(n-2)
* @param n
* @return
*/
private static int fibonacci(int n) {
if (n == 0 || n == 1) {
return 1;
} else {
return fibonacci(n - 1) + fibonacci(n - 2);
}
}