您的位置:首页 > 编程语言

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

2014-08-01 23:27 471 查看

问题:

斐波那契数列由如下递推关系式定义: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指向的指针数组以及该指针数组指向的数组元素。否则将会没机会释放这部分内存,造成内存泄露!


内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息