递归
递归,就是函数直接或间接调用自己的一种算法。个人理解和数学中的数列相类似。递归的递推关系就是数列的递推公式,递归出口就相当于数列的首项,而递归的参数就相当于数列的项数。
下面先看例子:
第五个人说他比第四个人多两岁,第四个人说他比第三个人多两岁,第三个人说他比第二个人多两岁,第二个人说他比第一个人多两岁,第一个人说自己10岁。问:第五个人多少岁?
针对这个问题我们可以轻易地写出其递推关系。首先定义一个能够表示第n个人年龄的函数Age(n)。根据题意,我们能够得到:
Age(5)=Age(4)+2
Age(4)=Age(3)+2
Age(3)=Age(2)+2
Age(2)=Age(1)+2
Age(1)=10
观察上述表达式关系,我们能够得到最终的递归关系:
A g e ( n ) = { 10 x=1 A g e ( n − 1 ) + 2 x>1 Age(n)= \begin{cases} 10& \text{x=1}\\ Age(n-1)+2& \text{x>1} \end{cases} Age(n)={10Age(n−1)+2x=1x>1
由此,我们就能写出递归算法,代码如下:
int Age(int n)
{
if(n == 1)
{
return 10;
}
else
{
return Age(n-1)+2;
}
}
我们将主函数补充完整。并将5以参数形式传入,其最终结果为:
递归的实现离不开栈。在递归中,未完成的函数将会被放入栈中,针对每一个函数都存在一个指向其返回值的栈帧,下面,我们用图来描绘递归的过程:
将其形式化为:
==> Age(5)
==> Age(4) + 2
==> ( Age(3) + 2) + 2
==>( (Age(2) + 2) + 2 ) + 2
==>( ( (Age(1) + 2 ) + 2 ) + 2 ) + 2
==>( ( (10 + 2 ) + 2 ) + 2 ) + 2
==>( ( 12 + 2 ) + 2 ) + 2
==> ( 14 + 2 ) + 2
==> 16 + 2
==> 18
循环
每一个递归都能写成循环形式,如,上面那串代码的循环形式为:
int Age(int n)
{
int tmp=10;
for(int i=1;i<n;i++)
{
tmp+=2;
}
return tmp;
}
相比递归来看,循环的运行时间和空间使用更少,效率更高,但是代码写起来比较麻烦,算法思路也不够清晰。
优化
为了减少递归的空间使用,提升效率,我们可以对递归进行优化,将其写成尾递归形式。其特点是在函数返回时只返回函数本身,其他的计算公式放到参数中实现。其代码如下:
int Age(int n,int age)
{
if(n==1)
{
return age;
}
else
{
return Age(n-1,age+2);
}
}
将其栈形式化为:
==>Age(5,10)
==>Age(4,12)
==>Age(3,14)
==>Age(2,16)
==>Age(1,18)
其本质和循环相类似,只是形式上偏向递归。