斐波那契数列解法

斐波那契数列解法

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));
}

 

 

 

 

 

 


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值