[编程之美] 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。
常见的解法如下:
我们的问题是:我们有没有更加优化的解法呢?
解法一:递推关系式的优化
由于在递归求解的过程中重复计算了多次,比如F(5)=F(4)+F(3);而在计算F(4)的时候又要重复的计算一次F(3)的大小···,因为每次计算都需要重复计算以前计算过的值,画出一棵递归树,每个节点都需要计算一遍。因此算法复杂度O(2^N)。
如果进行优化,可以使用一个数组存储Memo,对于计算过的不用重复计算,此时空间复杂度 O(N),时间复杂度也为O(N),如果使用自底向上的循环方法求解,也可以减少重复计算。代码如下:
这种方式需要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
代码如下:
问题:
斐波那契数列由如下递推关系式定义: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指向的指针数组以及该指针数组指向的数组元素。否则将会没机会释放这部分内存,造成内存泄露!
相关文章推荐
- 编程之美-2.9-斐波那契数列
- 编程之美 2.9 斐波那契数列
- [编程之美] PSet3.8 求二叉树中节点的最大距离
- [编程之美] PSet3.9 重建二叉树
- python基础编程_5_斐波那契数列
- [编程之美] PSet2.4 统计1的数目
- [编程之美] PSet2.6 精确表达浮点数
- [编程之美] PSet2.16 求数组中最长的递增子序列
- [编程之美] PSet3.4 从无头单链表中删除节点
- 80x86 汇编语言编程:斐波那契数列
- 编程之美之斐波那契数列
- 关于斐波那契数列的Java编程
- 编程之美2.9----斐波那契数列
- 编程之美_008斐波那契数列
- 【剑指Offer面试编程题】题目1387:斐波那契数列--九度OJ
- 斐波那契数列的几种编程实现及一般推广
- 编程之美2.9 斐波那契数列
- java语言的科学与艺术-编程练习2.9
- [编程之美] PSet1.10 双线程高效下载
- [编程之美] PSet2.5 寻找最大的K个数