递归函数的概念使用方法与实例
一、栈
在说函数递归的时候,顺便说一下栈的概念。
栈是一个后进先出的压入(push)和弹出(pop)式数据结构。在程序运行时,系统每次向栈中压入一个对象,然后栈指针向下移动一个位置。当系统从栈中弹出一个对象时,近期进栈的对象将被弹出。然后栈指针向上移动一个位置。程序猿常常利用栈这种数据结构来处理那些最适合用后进先出逻辑来描写叙述的编程问题。这里讨论的程序中的栈在每一个程序中都是存在的,它不须要程序猿编写代码去维护,而是由运行是系统自己主动处理。所谓的系统自己主动维护,实际上就是编译器所产生的程序代码。尽管在源码中看不到它们,但程序猿应该对此有所了解。
再来看看程序中的栈是怎样工作的。当一个函数(调用者)调用还有一个函数(被调用者)时,运行时系统将把调用者的全部实參和返回地址压入到栈中,栈指针将移到合适的位置来容纳这些数据。最后进栈的是调用者的返回地址。当被调用者開始运行时,系统把被调用者的自变量压入到栈中,并把栈指针再向下移,以保证有足够的空间存储被调用者声明的全部自变量。当调用者把实參压入栈后,被调用者就在栈中以自变量的形式建立了形參。被调用者内部的其它自变量也是存放在栈中的。由于这些进栈操作,栈指针已经移动全部这些局部变量之下。可是被调用者记录了它刚開始运行时的初始栈指针,以他为參考,用正或负的偏移值来訪问栈中的变量。当被调用者准备返回时,系统弹出栈中全部的自变量,这时栈指针移动了被调用者刚開始运行时的位置。接着被调用者返回,系统从栈中弹出返回地址,调用者就能够继续运行了。当调用者继续运行时,系统还将从栈中弹出调用者的实參,于是栈指针回到了调用发生前的位置。
可能刚開始学的人看不太懂上面的解说,栈涉及到指针问题,详细能够看看一些数据结构的书。要想学好编程语言,数据结构是一定要学的。
二、递归
递归,是函数实现的一个非常重要的环节,非常多程序中都或多或少的使用了递归函数。递归的意思就是函数自己调用自己本身,或者在自己函数调用的下级函数中调用自己。
递归之所以能实现,是由于函数的每一个运行过程都在栈中有自己的形參和局部变量的拷贝,这些拷贝和函数的其它运行过程毫不相干。这种机制是当代大多数程序设计语言实现子程序结构的基础,是使得递归成为可能。假定某个调用函数调用了一个被调用函数,再假定被调用函数又反过来调用了调用函数。这第二个调用就被称为调用函数的递归,由于它发生在调用函数的当前运行过程运行完成之前。并且,由于这个原先的调用函数、如今的被调用函数在栈中较低的位置有它独立的一组參数和自变量,原先的參数和变量将不受影响,所以递归能正常工作。程序遍历运行这些函数的过程就被称为递归下降。
程序猿需保证递归函数不会任意改变静态变量和全局变量的值,以避免在递归下降过程中的上层函数出错。程序猿还必须确保有一个终止条件来结束递归下降过程,并且返回到顶层。
比如这种程序就是递归:
void a(int);
main()
{
int num=5;
a(num);
}
void a(int num)
{
if(num==0) return;
printf("%d",num);
a(--num);
}
在函数a()里面又调用了自己,也就是自己调用本身,这样就是递归。那么有些人可能要想,这不是死循环吗?所以在递归函数中,一定要有return语句,没有return语句的递归函数是死循环。
我们分析上面的样例,先调用a(5),然后输出5,再在函数中调用本身a(4),接着回到函数起点,输出4,……,一直到调用a(0),这时发现已经满足if条件,不在调用而是返回了,所以这个递归一共进行了5次。假设没有这个return,肯定是死循环的。
尽管递归不难理解,可是非常多在在使用递归函数的时候,问题多多。这里面一般有两个原因:一是怎样往下递归,也就是不知道怎么取一个变量递归下去;二是不知道怎么终止递归,常常弄个死循环出来。
以下看几个样例:
1.求1+2+……+100的和
先分析一下。第一递归变量的问题,从题目上看应该取1,2,……,100这些变量的值作为递归的条件;第二就是怎样终止的问题,从题目上看应该是当数为100的时候就不能往下加了。那么我们试着写一下程序。
int add(int);
main()
{
int num=1,sn;
sn=add(num);
printf("%d/n",sn);
getch();
}
int add(int num)
{
static int sn;
sn+=num;
if(num==100) return sn;
add(++num);
}
分析一下程序:前调用add(1),然后在子函数中把这个1加到sn上面。接着调用add(2),再把sn加2上来。这样一直到100,到了100的时候,先加上来,然后发现满足了if条件,这时返回sn的值,也就是1+2+……+100的值了。
这里有一个问题一定要注意,就是static int sn;
有些人就不明确,为什么要使用static类型修饰符,为什么不使用int sn=0;?假设使用int sn=0;这种语句,在每次调用函数add()的时候,sn的值都是赋值为0,也就是第一步尽管加了1上来,可是第二次调用的时候,sn又回到了0。我们前面说了,static能保证本次初始化的值是上次运行后的值,这样也就保证了前面想加的结果不会丢失。假设你改动为int sn=0,最后结果一定是最后的100这个值而不是5050。
2.求数列s(n)=s(n-1)+s(n-2)的第n项。当中s(1)=s(2)=1。
能够看出,终止条件一定是s(1)=s(2)=1。递归下降的參数一定是n。
int a(int);
main()
{
int n,s;
scanf("%d",&n);
s=a(n);
printf("%d/n",s);
getch();
}
int a(int n)
{
if(n<3) return 1;
return a(n-1)+a(n-2);
}
这个题目主要说明的是,在函数中,不一定仅仅有一个return语句,能够有非常多,可是每次对归的时候仅仅有一个起作用。题目不难理解,这儿不分析了。
说了这些递归,事实上它和函数的调用没有大的差别,主要就是一个终止条件要选好。递归函数非常多时候都能用循环来处理。
main()
{
int n=20,array[20];
int i;
for(i=0;i<n;i++)
{
if(i<2) array[i]=1;
else array[i]=array[i-1]+array[i-2];
}
printf("%d/n",array[19]);
getch();
}
上面的程序就是实现一模一样的功能的。可是它有一个缺陷,就是n的值不是通过键盘输入来得到。假设想通过键盘来得到n,能够这样:
main()
{
int n,i;
int s1=1,s2=1,temp
scanf("%d",&n);
for(i=3;i<=n;i++)
{
temp=s2;
s2+=s1;
s1=temp;
}
printf("%d/n",s2);
getch();
}
可是在某些场合,使用递归比使用循环要简单的多。并且有些题目,一看就知道应该使用递归而不是循环来处理