在C语言中,递归指的是函数在函数体内部调用了函数自身。这种形式对于解决一些能够用分治法将主问题分解成若干子问题,且子问题的结构与主问题相似的问题的时候,可以极大地简化程序的编写,但是递归的思想对于初学者来说一般都难以接受。其实递归函数的使用主要抓住两点,第一是主问题与子问题之间的拆分与组合关系,第二就是递归的结束条件,清楚这两个要素再来编写递归函数就会简单多了。
但是递归函数也有一些隐含的缺点,这就要从函数调用本质说起了。首先从整体上看,函数调用是从一个正在执行的程序A跳转到另外一个程序B去执行,当执行完程序B后继续回到程序A中执行,这就要求CPU在从程序A跳转出去之前先将程序A的“现场”,在执行完B后再将A的“现场”恢复。这个保存和恢复现场的操作是通过程序堆栈来实现的。
函数的递归调用与普通的函数调用的区别是,递归调用的嵌套可能会很深,也就是说程序A调用程序A1,A1有调用A2,A2调用A3,........ 直到满足递归终止条件。这就带来了以下几个问题:
1、在遇到递归终止条件之前不断地将程序现场压栈,可能会导致堆栈溢出。
2、保存现场和恢复现场带来了额外的开销。
3、可能会产生过多的冗余计算,浪费计算资源(比如递归实现斐波那契)。
下面通过一个例子来比较一下递归的利与弊:
计算斐波那契数列: F(n)= F(n-1)+F(n-2)
一、递归实现:
long F(int n)
{
if(n<=2) return 1;
return F(n-1)+F(n-2);
}
从编程角度来说非常简洁,两行代码就完成了,而且理论上完全没问题。
但是:计算一下F(10),要前反复调用94次F(n),嵌套8层,最糟糕的是F(2)被重复计算了31次,F(3)被重复计算了21次,F(4)被重复计算了13次,.......而且这些重复的计算其计算过程和结果是完完全全一样的。可见计算的“性价比”是多么低。(当F(n)的n更大时,反复调用F(N)的次数,以及被重复计算的次数将接近于呈指数的增长,在计算F(30)时,在不考虑堆栈溢出的情况下,F(3)将被重复计算317811次,.........)。
二、非递归实现:
long F(int n )
{
long result;
long pre_result;
long pre_pre_result;
result = pre_result = 1;
while(n>2){
n=n-1;
pre_pre_result = pre_result;
pre_result = result;
result = pre_result + pre_pre_result;
}
return result;
}
虽然在程序的实现上非递归的形式略微繁琐了一点,但是相对于递归形式实现来说,其效率提高了几十万倍不止!所以,递归有递归的好处,在一些特殊问题求解上会非常方便,但也有他的坏处,在资源有限的处理器上运行时,应谨慎使用!