本文将从两个简单代码出发,对具有连续两次递归调用的程序进行粗浅分析,试图从堆栈的角度对其进行解释。
首先,什么是递归函数?
在数学与计算机科学中,递归(Recursion)是指在函数的定义中使用函数自身的方法。实际上,递归,顾名思义,其包含了两个意思:递 和 归,这正是递归思想的精华所在。
正如上面所描述的场景,递归就是有去(递去)有回(归来),如下图所示。“有去”是指:递归问题必须可以分解为若干个规模较小,与原问题形式相同的子问题,这些子问题可以用相同的解题思路来解决;“有回”是指 : 这些问题的演化过程是一个从大到小,由近及远的过程,并且会有一个明确的终点(临界点),一旦到达了这个临界点,就不用再往更小、更远的地方走下去。最后,从这个临界点开始,原路返回到原点,原问题解决。
由此可看出,对于递归函数的程序编写角度而言,有两个关键点必须紧紧抓住。
(1)递归出口:即递归的结束条件,到何时不再递归调用下去,开始归来。
(2)递归式子:当前函数结果与准备调用的函数结果之间的关系,如求阶乘函数Factorial(N)=N*Factorial(N-1)。
理解完递归后,先来看第一个程序。
#include <stdio.h>
void rec(int N);
int main()
{
rec(11);
return 0;
}
void rec(int N) {
if (N>0){
rec(N - 10);//rec1
rec(N - 1);//rec2
}
printf("%d\n",N); //此时的打印就相当于出栈
printf("*******\n");
return;
}
打印结果为:
-9
*******
0
*******
1
*******
0
*******
-1
*******
-2
*******
-3
*******
-4
*******
-5
*******
-6
*******
-7
*******
-8
*******
-9
*******
0
*******
1
*******
2
*******
3
*******
4
*******
5
*******
6
*******
7
*******
8
*******
9
*******
10
*******
11
*******
第一步:先将N=11压栈,明显满足递归条件,故进行rec1的递归压栈,当N=-9时不满足递归条件就开始出栈;
第二步:rec1开始出栈时,栈顶元素为-9,故N=-9出栈。此时栈顶为N=1,满足递归条件,按照代码顺序执行,则rec1出栈操作中断,转而执行rec2的入栈操作,递归之后N=0(N-1)入栈,很明显N=0不满足递归条件故0出栈,也就是说rec2递归结束开始出栈(即0出栈)。
第三步:前面提到,在中断rec1出栈操作后才执行rec2的递归入栈操作的,因此在rec2结束之后转回来接着执行rec1的出栈操作,即当前栈顶N=1出栈。
第四步:当前栈顶N=11,满足递归条件,按照代码顺序执行rec2的入栈操作,一次递归后N=10。此时仍然满足递归条件,故按照代码顺序执行rec1入栈,递归后N=0入栈。很明显N=0不满足递归条件故0出栈。
第五步:当前栈顶N=10,满足递归条件,按照代码顺序执行rec2的入栈操作,以此类推。
下面是出入栈的部分过程
且看第二个程序。
#include <stdio.h>
void fun(int n)
{
n--;
if ( 0 == n ) //跳出递归的条件
{
printf("end\n");
return ;//出栈
}
printf("fun --> %d\n" ,n);//fun函数的值
fun(n);//fun1
fun(n);//fun2
//printf("fun --> %d\n" ,n);
}
int main()
{
fun(5);
}
打印结果为:
fun --> 4
fun --> 3
fun --> 2
fun --> 1
end
end
fun --> 1
end
end
fun --> 2
fun --> 1
end
end
fun --> 1
end
end
fun --> 3
fun --> 2
fun --> 1
end
end
fun --> 1
end
end
fun --> 2
fun --> 1
end
end
fun --> 1
end
end
第一步:输入5入栈,则fun1递归4,故第一次递归输出 4 3 2 1并入栈,最后栈顶为n=0(1--)。此时明显n=0满足递归出口条件,结束fun1递归开始出栈,即0出栈。
第二步:此时n=1,按照代码顺序执行fun2,即中断fun1出栈操作开始执行fun2。fun2递归得到n=0,即栈顶为0,明显满足递归出口条件,结束fun2递归开始出栈,即0出栈。
第三步:此时栈顶为n=1。继续fun1出栈操作,即1出栈。此时n=2,按照代码顺序执行fun2,即中断fun1出栈开始执行fun2。注意,此时n=2,n--后得到n=1,按照代码顺序执行fun1,n=1、n=0入栈,得到栈顶为n=0。
第四步:n=0满足递归出口条件,结束fun1递归开始出栈,即0出栈。此时栈顶为n=1,按照代码顺序执行fun2,即中断fun1出栈开始执行fun2。fun2递归得到n=0,结束fun2递归且n=0出栈。
第五步:继续fun1出栈,即n=1、n=2出栈。此时栈顶为n=3,则继续中断fun1出栈操作开始执行fun2。
第六步:以此类推。
下面是出入栈的部分过程:
以上只是一些粗浅思考,真正理解递归的堆栈过程还是回归到程序调试上,一步一步调试查看变量。