[编程之美] PSet2.9 斐波那契数列



问题:

斐波那契数列由如下递推关系式定义:F(0) = 0,F(1)=1,F(n)=F(n-1)+F(n-2) if n>1。

常见的解法如下:

//递归解法解Fibonacci数列
int Fibonacci(int n)
{
	if(n<0){
		cerr<<"Fibonacci数列项数不能为负值\n"<<endl;
		return -1;
	}
	if(n==1 || n==0)
		return n;
	else
		return Fibonacci(n-1)+Fibonacci(n-2);
}

我们的问题是:我们有没有更加优化的解法呢?


解法一:递推关系式的优化

由于在递归求解的过程中重复计算了多次,比如F(5)=F(4)+F(3);而在计算F(4)的时候又要重复的计算一次F(3)的大小···,因为每次计算都需要重复计算以前计算过的值,画出一棵递归树,每个节点都需要计算一遍。因此算法复杂度O(2^N)。

如果进行优化,可以使用一个数组存储Memo,对于计算过的不用重复计算,此时空间复杂度 O(N),时间复杂度也为O(N),如果使用自底向上的循环方法求解,也可以减少重复计算。代码如下:

//循环方式解Fibonacci数列
int Fibonacci(int n)
{
	int f0=0,f1=1,result=-1;
	if(n<0){
		cerr<<"Fibonacci数列项数不能为负\n"<<endl;
		exit(-1);
	}
	if(n == 0)
		return f0;
	else if(n==1)
		return f1;
	for(int i=2 ; i<=n ; i++){
		result = f0+f1;
		f0=f1;
		f1=result;
	}
	return result;
}

这种方式需要O(N)级别的时间复杂度,挺不错的,只是需要对这个数列有非常清晰的把握。


解法二:求解通项公式

由递推公式F(n) = F(n-1)+F(n-2)知道f(n)的特征方程为 x*x = x + 1,根为s1=(1+sqrt(5))/2 和s2=(1-sqrt(5))/2。那么存在A,B使得:f(n)=A*s1^n + B*s2^n,代人f(0)=0,f(1)=1,得到A=sqrt(5)/5,B=-sqrt(5)/5。即f(n)=A*s1 + B*s2。因此只需要运算一次就能在常数时间O(1)内得到结果,但是公式中引入了无理数,不能保证结果的精度。


解法三:分治策略

注意到斐波那契数列是二阶递推数列,所以存在一个2*2的矩阵A,使得:
(Fn, Fn-1) = (Fn-1, Fn-2)*A
求得A=(1 1)
            (1 0)

于是(Fn,Fn-1) = (F1 , F0) * A^(n-1)
那么求数列的第n项就是等于求矩阵A的第n-1次幂。因为计算A的n-1乘法有快速相乘的方法,比如计算m的10000次方,其实最少的计算次数,等于10000的最高比特位的位置与零的个数,即14次左右乘法运算(计算2^i<10000的i的最大值),并不需要10000次傻乘。

快速相乘的方法:

想要求A^n,那么由n的二进制表示为:

可以很容易的通过logn次乘法得到A^n,举例子:A^5=(A^2)^2 * A

代码如下:

//定义整数 Martix类
class Matrix{
private:
	int row,col;
	int **dat;
public:
	//Matrix();//不允许默认构造函数
	Matrix(int r, int c);
	/*在C++中,下面三种对象需要拷贝的情况。因此,拷贝构造函数将会被调用。
		1). 一个对象以值传递的方式传入函数体
		2). 一个对象以值传递的方式从函数返回
		3). 一个对象需要通过另外一个对象进行初始化*/
	Matrix(const Matrix&mat);

	~Matrix();
	int Row(){return this->row;}
	int Col(){return this->col;}
	Matrix MatrixMult(const Matrix &mat)const;
	Martix& operator= (const Matrix &mat);
	int* operator[] (int row){return this->dat[row];}//返回第row行的行指针
	const int*operator[](int row)const{return this->dat[row];}// 返回第row行的常量指针,指向常量的指针
	

	friend istream& operator>> (istream& in , Matrix& mat);
	friend ostream& operator<< (ostream& out , Matrix& mat);
};
//拷贝构造函数,此处有dat指针,应避免浅拷贝
Matrix::Matrix(const Matrix&mat)
{
	this->row = mat.row;
	this->col = mat.col;
	
	//复制数据块
	dat = new int* [row];//指针数组
	for(int i=0 ; i<row ; i++){
		dat[i] = new int[col];
		memset(dat[i] , 0 , sizeof(int)*col);
	}
	for(int i=0 ; i<mat.row ; i++)
		for(int j=0 ; j<mat.col ; j++)
			this->dat[i][j] = mat.dat[i][j];
}
Matrix::Matrix(int r, int c)
{ 
	this->row = r;
	this->col = c;
	dat = new int* [row];//指针数组
	for(int i=0 ; i<row ; i++){
		dat[i] = new int[col];
		memset(dat[i] , 0 , sizeof(int)*col);
	}
}
Matrix::~Matrix()
{
	//先释放该指针数组每个元素指向的数组,再释放该指针数组
	for(int i=0 ; i<row ; i++){
		delete [col]dat[i];
		dat[i] = NULL;
	}
	delete [row]dat;
	dat = NULL;
}
Martix& Matrix::operator= (const Matrix &mat)
{	if(this == &mat) return *this;
	this->col = mat.col;
	this->row = mat.row;
	for(int i=0 ; i<row ; i++)
		for(int j=0 ; j<col ; j++)
			(*this)[i][j] = mat[i][j];return *this;//返回本身的引用
}
istream& operator>> (istream& in , Matrix& mat)
{
	for(int i=0 ; i<mat.row ; i++)
		for(int j=0 ; j<mat.col ; j++)
			in>>mat.dat[i][j];
	return in;
}

ostream& operator<< (ostream& out , Matrix& mat)
{
	for(int i=0 ; i<mat.row ; i++){
		for(int j=0 ; j<mat.col ; j++){
			out<<mat.dat[i][j]<<" ";
			if(j == mat.col-1)//换行
				out<<endl;
		}
	}
	return out;
}
Matrix Matrix::MatrixMult(const Matrix &mat)const
{
	assert(this->col == mat.row);
	int tempRow = this->row;
	int tempCol = mat.col;
	Matrix temp(tempRow , tempCol);
	for(int i=0 ; i<tempRow ; i++){
		for(int j=0 ; j<tempCol ; j++){
			for(int k=0 ; k<this->col ; k++)//k遍历所有待乘的数
				temp[i][j] += (*this)[i][k]*mat[k][j];
		}
	}
	return temp;//调用拷贝构造函数
}
int Fibonacci(int N)
{
	Matrix res(2,2);
	if (N==0) return 0;  
	--N;    // 计算矩阵prod的n-1次幂  
	res[0][0] = 1; res[0][1] = 0;   
	res[1][0] = 0; res[1][1] = 1;  
	Matrix prod(2,2); 
	prod[0][0] = 1;  prod[0][1] = 1;  
	prod[1][0] = 1;  prod[1][1] = 0;  
	// 只需要O(logn)的复杂度就能算出x的n次幂    
	while(N)
	{
		if(N&1)
			res = res.MatrixMult(prod);
		prod = prod.MatrixMult(prod);
		N=N>>1;
	}
	return res[0][0];  
}

int main()
{

	cout<<Fibonacci(20)<<endl;
	return 0;
}
注意:本人突然意识到本代码中Martix类重载的赋值运算符有问题!会产生内存泄露,正确的应该在复制mat数据到*this之前应该清空*this的指针dat指向的指针数组以及该指针数组指向的数组元素。否则将会没机会释放这部分内存,造成内存泄露!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值