斐波那契数列解法
1、概念
在数学上,费波那契数列是以递归的方法来定义:
F0=0
F1=1
Fn=Fn-1+Fn-2(n≧2)
用文字来说,就是费波那契数列由0和1开始,之后的费波那契系数就是由之前的两数相加而得出。首几个费波那契系数是:
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233……(OEIS中的数列A000045)
特别指出:0不是第一项,而是第零项。
2、解法
2.1递归解法
(1)代码
/*斐波那契递归解法*/
#include <iostream>
#include <string>
using namespace std;
long Fibonacci(unsigned int n){
if(n < 2){
return n;
} else {
return Fibonacci(n-1) + Fibonacci(n-2);
}
}
int main()
{
cout<<"Enter an number:"<<endl;
unsigned int num=0;
cin>>num;
cout<<"the "Fibonacci(num)<<endl;
return 0;
}
(2)分析
当我们计算Fibonaccinacci(5)时:
为了计算Fibonaccinacci (5),需要计算Fibonacci(3),Fibonacci(4);而为了计算Fibonacci(4),需要计算Fibonacci(2),Fibonacci(3)……最终为了得到Fibonacci(5)的结果,Fibonacci(0)被计算了3次,Fibonacci(1)被计算了5次,Fibonacci(2)被计算了2次。可以看到,它的计算次数几乎是指数级的!
因此,虽然递归算法简洁,但是在这个问题中,它的时间复杂度却是难以接受的。除此之外,递归函数调用的越来越深,它们在不断入栈却迟迟不出栈,空间需求越来越大,虽然访问速度高,但大小是有限的,最终可能导致栈溢出。
2.2 递归改进
2.1版本的递归存在大量的重复计算,可以考虑将已经计算的值保存起来,从而避免重复计算,代码实现如下:
/*斐波那契递归改进*/
long FibonacciProcess(unsigned long *array, unsigned int n){
if(n<2){
return n;
} else {
/*递归保存值*/
array[n]=FibonacciProcess(array,n-1)+array[n-2];
return array[n];
}
}
long Fibonacci(unsigned int n){
if(n<=1){
return n;
}
unsigned long ret=0;
//申请数组用于保存已经计算过的内容
unsigned long *array=(unsigned long*)calloc(n+1, sizeof(unsigned long));
if(array == NULL){
return -1;
}
array[1]=1;
ret=FibonacciProcess(array,n);
free(array);
array=NULL;
return ret;
}
可见改进版本,时间复杂度为O(n),避免了重复计算,但是调用链仍然比较长。
2.3 迭代解法
递归法不够优雅。如果不用计算机计算,让你去算第n个斐波那契数,会怎么做?最简单直接的方法是:知道第一个和第二个,计算第三个;知道第二个和第三个,计算第四个,以此类推。最终可以得到我们需要的结果。这种思路没有冗余的计算。代码实现如下:
/*斐波那契数迭代解法*/
long FibonacciIterration(unsigned int n){
unsigned long preVal=1;
unsigned long prePreVal=0;
if(n<2){
return n;
}
unsigned int loop=1;
unsigned long returnVal=0;
while(loop < n){
returnVal = preVal+prePreVal;
//更新记录结果
prePreVal=preVal;
preVal=returnVal;
loop++;
}
return returnVal;
}
时间复杂度为O(n)。
2.4 尾递归解法
要计算第n个斐波那契数,我们可以先先计算第一个,第二个,如果未达到n,则继续递归计算,尾递归实现代码如下:
/*斐波那契数尾递归解法*/
long FibonacciTialRecursionProcess(unsigned int n, unsigned long prePreVal, unsigned long preVal,unsigned long begin){
//如果已经计算到我们需要计算的,则返回
if(n == begin)
{
return prePreVal+preVal;
} else {
begin++;
return FibonacciTialRecursionProcess(n,preVal,prePreVal+preVal,begin);
}
}
long Fibonacci(unsigned int n){
if(n <= 1){
return n;
} else {
return FibonacciTialRecursionProcess(n,0,1,2);
}
}
尾递归效率并不逊于迭代法。尾递归在函数返回之前的最后一个操作仍然是递归调用。尾递归的好处是,进入下一个函数之前,已经获得了当前函数的结果,因此不需要保留当前函数的环境,内存占用自然也是比最看是提到的递归要小。时间复杂度为O(n)。
2.5 矩阵快速幂解法
这是一种高效的解法,推导过程如下:
如果a为矩阵,等式同样成立。
假设有矩阵2*2矩阵A,满足下面的等式:
可以得到矩阵A:
因此也就可以得到下面的矩阵等式:
再进行变换如下:
以此类推,得到:
实际上f(n)就是矩A^(n-1)中的A[0][0],或者是矩A^n中的A[0][1]。
现在的问题就归结为,如何求A^n,其中A为2*2的矩阵。根据我们最开始的公式,很容易就有思路,代码实现如下:
/*矩阵快速幂解法*/
#define MAX_COL 2
#define MAX_ROW 2
typedef unsigned long MatrixType;
//计算2*2矩阵乘法,这里没有写成通用形式的矩阵乘法
int matrixDot(MatrixType A[MAX_ROW][MAX_COL],MatrixType B[MAX_ROW][MAX_COL],MatrixType C[MAX_ROW][MAX_COL]){
//C为返回结果,由于A可能和C相同,因此使用临时矩阵存储
MatrixType tempMa[MAX_ROW][MAX_COL];
memset(tempMa,0,sizeif(tempMa));
tempMa[0][0]=A[0][0]*B[0][0]+A[0][1]*B[1][0];
tempMa[0][1]=A[0][0]*B[0][1]+A[0][1]*B[1][1];
tempMa[1][0]=A[1][0]*B[0][0]+A[1][1]*B[1][0];
tempMa[1][1]=A[1][0]*B[0][1]+A[1][1]*B[1][1];
memcpy(C,tempMa,sizeof(tempMa));
return 0;
}
MatrixType Fibonacci(int n){
if(n <= 1){
return n;
}
MatrixType result[][MAX_COL]={1,0,0,1};
MatrixType A[][2]={1,1,1,0};
while(n > 0){
//判断最后一位是否为1,即可知奇偶
if(n & 1){
matrixDot(result,A,result);
}
n /= 2;
matrixDot(A,A,A);
}
return result[0][1];
}
该算法的关键部分在于对A^n的计算,它利用了我们开始提到的等式,对奇数和偶数分别处理。假设n为9,初始矩阵为INIT则计算过程如下:
·9为奇数,则计算INIT*A,随后A变为A*A,n变为9/2,即为4
·4为偶数,则结果仍为INIT*A,随后A变为(A2)*( A2)=A4,n变为4/2,即2
·2为偶数,则结果仍为INIT*A,随后A变为(A4)*( A4)=A8,n变为2/2,即1
·1为奇数,则结果为INIT*(A^8)*A
可以看到,计算次数类似于二分查找次数,其时间复杂度为O(logn)。
2.6 通项公式解法
斐波那契数列的通项公式为:
代码实现如下:
/*斐波那契数列通项公式解法*/
long Fibonacci(unsigned long n){
if(n<1){
return n;
}
return (unsigned long)((pow((1+sqrt(5))/2,n)-pow((1-sqrt(5))/2,n))/sqrt(5));
}