什么是斐波那契数列,1,1,2,3,5,8,13...这样一个数列就是斐波那契数列,求第n项的值
递归
关于求斐波那契数列的经典递归方法有:
除了第一项和第二项,所有的数列的值都是前一项和前一项的前一项的加和,转换成函数也就是f(n) = f(n-1) + f(n-2)
int FIB(int n)
{
if (n <= 2)
return 1;
else
return FIB(n - 1) + FIB(n - 2);
}
当我们n输⼊为50的时候,需要很⻓时间才能算出结果,这个计算所花费的时间,是我们很难接受的,
这也说明递归的写法是⾮常低效的,那是为什么呢?
其实递归程序会不断的展开,在展开的过程中,我们很容易就能发现,在递归的过程中会有重复计
算,⽽且递归层次越深,冗余计算就会越多。
例如:我们可以统计第三个数统计次数:
#include<stdio.h>
int count = 0;
int FIB(int n)
{
if (n == 3)
count++;
if (n <= 2)
return 1;
else
return FIB(n - 1) + FIB(n - 2);
}
int main()
{
int n = 0;
scanf_s("%d", &n);
int m=FIB(n);
printf("%d\n", m);
printf("%d\n", count);
return 0;
}
当输入40时,得到如下结果:
102334155
39088169
这⾥我们看到了,在计算第40个斐波那契数的时候,使⽤递归⽅式,第3个斐波那契数就被重复计算了39088169次,这些计算是⾮常冗余的。它的时间复杂度十分高的!
时间复杂度:
时间复杂度实际就是一个函数,该函数计算的是执行基本操作的次数。
迭代
我们知道斐波那契数的前2个数都1,然后前2个数相加就是第3个数,那么我们从前往后,从⼩到⼤计算就⾏了。
就是从第三位开始以次计算,每计算一次就将abc往后移动一位:
int FIB(int n)
{
int a = 1;
int b = 1;
int c = 1;
while (n >= 3)
{
c = a + b;
a = b;
b = c;
n--;
}
return c;
}
尾递归
定义:如果一个函数中所有递归形式的调用都出现在函数的末尾,我们称这个递归函数是尾递归的。当递归调用是整个函数体中最后执行的语句且它的返回值不属于表达式的一部分时,这个递归调用就是尾递归。
其运行效率变高是因为所使用的栈空间大大减小;当编译器检测到一个函数调用是尾递归的时候,它就覆盖当前的活动记录而不是在栈中去创建一个新的。因为递归调用是当前活跃期内最后一条待执行的语句,于是当这个调用返回时栈帧中并没有其他事情可做,因此也就没有保存栈帧的必要了。
int FIB(int n,int a,int b)
{
if (n <= 2)
return b;
else
return FIB(n - 1, b, a + b);
}
总结
虽然计算斐波那契数列用迭代法和尾递归法可以提高运行效率,但其空间复杂度较高。
空间复杂度是对算法运行过程中临时占用空间大小的度量
就会出现下面现象:
分析一个算法所占用的存储空间要从各方面综合考虑。如对于递归算法来说,一般都比较简短,算法本身所占用的存储空间较少,但运行时需要一个附加堆栈,从而占用较多的临时工作单元;若写成非递归算法,一般可能比较长,算法本身占用的存储空间较多,但运行时将可能需要较少的存储单元。