http://www.nowamagic.net/librarys/veda/detail/2300
在高级语言中,调用自己和其他函数并没有本质的不同。我们把一个直接调用自己或通过一系列的调用语句间接地调用自己的函数,称做递归函数。
-
当然,写递归程序最怕的就是陷入永不结束的无穷递归中,所以,毎个递归定义必须至少有一个条件,满足时递归不再进行,即不再引用自身而是返回值退出。比如刚才的例子,总有一次递归会使得i < 2的,这样就可以执行return i的语句而不用继续递归了。
对比了两种实现斐波那契的代码。迭代和递归的区别是:迭代使用的是循环结构,递归使用的是选择结构。
- 递归能使程序的结构更清晰、更简洁、更容易让人理解,从而减少读懂代码的时间。但是大量的递归调用会建立函数的副本,会耗费大量的时间和内存。
- 迭代则不需要反复调用函数和占用额外的内存。因此我们应该视不同 情况选择不同的代码实现方式。
那么我们讲了这么多递归的内容,和栈有什么关系呢?这得从计算机系统的内部说起。
-
前面我们已经看到递归是如何执行它的前行和退回阶段的。递归过程退回的顺序是它前行顺序的逆序。在退回过程中,可能要执行某些动作,包括恢复在前行过程中存储起来的某些数据。
这种存储某些数据,并在后面又以存储的逆序恢复这些数据,以提供之后使用的需求,显然很符合栈这样的数据结构,因此,编译器使用栈实现递归就没什么好惊讶的了。
简单的说,就是在前行阶段,对于每一层递归,函数的局部变量、参数值以及返回地址都被压入栈中。在退回阶段,位于栈顶的局部变量、参数值和返回地址被弹出,用于返回调用层次中执行代码的其余部分,也就是恢复了调用的状态。
-
递归调用其实就是栈,栈有先进后出的特点,递归调用的实质也就是循环调用,我写一个简单的例子吧:
01 |
#include "stdio.h" |
02 |
03 |
int main() |
04 |
{ |
05 |
int n; |
06 |
int f( int m); |
07 |
printf ( "请输入一个大于1的数:" ); |
08 |
scanf ( "%d" ,&n); |
09 |
printf ( "%d\n" ,f(n)); |
10 |
return 0; |
11 |
} |
12 |
13 |
int f( int m) |
14 |
{ |
15 |
if (m==1) |
16 |
return 1; |
17 |
else |
18 |
{ |
19 |
printf ( "m=%d\n" ,m); |
20 |
//每当m!=1的时候就不断地调用int f(int m),直到m==1为止 |
21 |
return f(m-1); |
22 |
} |
23 |
} |
程序执行结果:
1 |
请输入一个大于1的数:5 |
2 |
m=5 |
3 |
m=4 |
4 |
m=3 |
5 |
m=2 |
6 |
1 |
-
这个程序是这样执行的,首先你先随便输入一个整数n,对于每个整数把n的值传给m,这样就实现了函数的第一次调用。然后调用了f(n),紧接着执行int f(int m),当m不等于1的时候就执行了else语句,return f(m-1),这样f(m-1)又一次调用了int f(int m),直到m=1为止。最终返回了f(n)的值,递归调用也到此为止。
下面附上动画演示:
当然,对于现在的髙级语言,这样的递归问题是不需要用户来管理这个栈的,一切都由系统代劳了。
延伸阅读
此文章所在专题列表如下:
- 栈的定义与大概理解
- 栈的抽象数据类型ADT
- 顺序栈:栈的顺序存储结构
- 顺序栈的进栈操作
- 顺序栈的出栈操作
- 获取顺序栈的栈顶元素
- 链栈:栈的链式存储结构
- 链栈的进栈操作
- 链栈的初始化与遍历
- 链栈的出栈操作
- 链栈的置空操作与判断链栈是否为空
- 为什么要使用栈这种数据结构
- 递归,栈的重要应用之一
- 栈是如何实现递归的
- 接触后缀表达式(逆波兰表示法)
- 图解后缀表达式的计算过程
- 将中缀表达式转化为后缀表达式
- 开始学习队列这个数据结构
- 队列的抽象数据类型ADT
- 顺序队列:队列的顺序存储结构
- 顺序队列的入队操作
- 顺序队列的出队操作
- 顺序队列置空与判断操作
- 队列顺序存储结构的不足
- 关于循环队列的一些讲解
- 链队列:队列的链式存储结构
- 链队列的初始化操作
- 链队列的入队操作
- 链队列的出队操作
- 补完链队列的其它常见操作
- 循环队列与链队列的优劣势