题目:斐波那契数列,又称黄金分割数列(F(n+1)/F(n)的极限是1:1.618,即黄金分割率),指的是这样一个数列:0、1、1、2、3、5、8、13、21、34、……。在数学上,斐波纳契数列以如下被以递归的方法定义:

              F(0)=0F(1)=1F(n)=F(n-1)+F(n-2)(n≥2,n∈N*)

  递归实现——自上而下

  

  在很多C语言教科书中讲到递归函数的时候,都会用Fibonacci作为例子。因此很多程序员对这道题的递归解法非常熟悉,看到题目就能写出如下递归求解的代码:

long Fibonacci(long n)                          //递归算法
{
if(n<=) return n; //终止递归的条件
else return Fibonacci(n-) + Fibonacci(n-);//递归步骤
}

  

  但是,教科书上反复用这个题目来讲解递归函数,并不能说明递归解法最适合这道题目。我们以求解F(10)作为例子来分析递归求解的过程。要求得F(10),需要求得F(9)和F(8)。同样,要求得F(9),要先求得F(8)和F(7)……我们用下面的树形结构来表示这种依赖关系

         F(10)
                         /             \
                    F(9)          F(8)
                   /     \           /    \
               F(8)    F(7)   F(7)   F(6)
              /    \      /    \ 
           F(7)  F(6) F(6)  F(5)

  我们不难发现在这棵树中有很多结点会重复的,而且重复的结点数会随着n的增大而急剧增加。这意味这计算量会随着n的增大而急剧增大。例如,在递归计算F(10)时,F(3)的值被计算了21次。而在递归计算F(30),这个调用的次数是骇人的317811次!这些个计算实际上只有一次是必要的,其余的纯属浪费!

  事实上这个递归算法的时间复杂度是指数级Ω(φn),φ=1.618(1:1.618=0.618称为黄金分割率)。

  迭代算法——自底向上

  下面的程序使用一个简单循环迭代来代替递归,这个非递归的形式不如上文给出的递归简单,也不太符合Fibonacci的递归定义,但是,它的运行速度提高了特别多!

  迭代算法的源码如下:

// 计算斐波那契数列的非递归算法(迭代)
long Fibonacci(long n)
{
if(n<=) return n; // Fib(0)或Fib(1)的情况
long FibCurrent, FibTwoBack = , FibOneBack = ; // 用数组保存程序更简洁,但不能明显的看出迭代的思想
for(int i= ; i<=n ; i++) // n≥2的情况
{
FibCurrent = FibOneBack + FibTwoBack; // 计算Fib(i)=Fib(i-1)+Fib(i-2)
/* 下面的保存顺序不能对调 */
FibTwoBack = FibOneBack; // 保存Fib(i-1)作为下趟的Fib(i-2)
FibOneBack = FibCurrent; // 保存Fib(i)作为下趟的Fib(i-1) }
return FibCurrent;
}

  显然,这个算法的时间复杂度为O(n),相比于前面指数级的递归算法,有了质的飞跃。

  事实上,这还不是最快的算法。还有一种时间复杂度是O(logn)的方法

  转化为特征矩阵乘方——分治策略 + 矩阵快速幂

  由数学归纳法易证:

          

  问题转化为求,继而就求出了F(n)。

  对于乘方问题我们利用分治策略优化有 

  运行时间递归式:T(n) = T(n/2) + θ(1) (同二分搜索一样)    用主方法接得T(n) = θ(logn)

  利用对特征矩阵乘方优化的方法可以得到最小的运行时间复杂度O(logn),代码如下:

class Matrix    // 自定义2×2矩阵类
{
public:
unsigned int a11, a12, a21, a22; // 矩阵元素
Matrix(int a, int b, int c, int d) :a11(a), a12(b), a21(c), a22(d) {}// 构造函数
Matrix operator*(const Matrix &other) // 重载矩阵的乘法
{
Matrix result(, , , );
result.a11 = a11*other.a11 + a12*other.a21;
result.a12 = a11*other.a12 + a12*other.a22;
result.a21 = a21*other.a11 + a22*other.a21;
result.a22 = a21*other.a12 + a22*other.a22;
return result;
}
}; Matrix MatrixPow(const Matrix &A, unsigned int n)// 计算矩阵A的n次方(分治策略,此处自底向上迭代)
{
Matrix result(, , , ); // 单位矩阵
Matrix tmp = A;
while (n)
{
if (n & ) // &为按位"与"运算,如果n为奇数
result = result * tmp;// 单乘一次矩阵
tmp = tmp * tmp;
n = n >> ; // n右移一位,相当于n/2(向下取整)
}
return result;
} unsigned int Fibonacci(int n)
{
if (n <= )
return n;
Matrix A(, , , ); // 特征矩阵
Matrix result = MatrixPow(A, n); // 计算矩阵A的n次方
return result.a12; // Fn即为结果矩阵中第一行第二例上的元素
}

    参考资料: 《MIT算法导论公开课》第三集——分治法

          《编程之美》P163

          《剑指offer》P73

斐波那契(Fibonacci)数列的几种计算机解法的更多相关文章

  1. 斐波那契(Fibonacci)数列的七种实现方法

    废话不多说,直接上代码 #include "stdio.h" #include "queue" #include "math.h" usin ...

  2. 如何用Python输出一个斐波那契Fibonacci数列

    a,b = 0, 1 while b<100: print (b), a, b = b, a+b

  3. 斐波那契 (Fibonacci)数列

    尾递归会将本次方法的结果计算出来,直接传递给下个方法.效率很快. 一般的递归,在本次方法结果还没出来的时候,就调用了下次的递归, 而程序就要将部分的结果保存在内存中,直到后面的方法结束,再返回来计算. ...

  4. 2019.8.3 [HZOI]NOIP模拟测试12 A. 斐波那契(fibonacci)

    2019.8.3 [HZOI]NOIP模拟测试12 A. 斐波那契(fibonacci) 全场比赛题解:https://pan.baidu.com/s/1eSAMuXk 找规律 找两个节点的lca,需 ...

  5. ACM/ICPC 之 数论-斐波拉契●卢卡斯数列(HNNUOJ 11589)

    看到这个标题,貌似很高大上的样子= =,其实这个也是大家熟悉的东西,先给大家科普一下斐波拉契数列. 斐波拉契数列 又称黄金分割数列,指的是这样一个数列:0.1.1.2.3.5.8.13.21.34.… ...

  6. 递归函数练习:输出菲波拉契(Fibonacci)数列的前N项数据

    /*====================================================================== 著名的菲波拉契(Fibonacci)数列,其第一项为0 ...

  7. [洛谷P3938]:斐波那契(fibonacci)(数学)

    题目传送门 题目描述 小$C$养了一些很可爱的兔子.有一天,小$C$突然发现兔子们都是严格按照伟大的数学家斐波那契提出的模型来进行繁衍:一对兔子从出生后第二个月起,每个月刚开始的时候都会产下一对小兔子 ...

  8. HZOJ 斐波那契(fibonacci)

    先说一个规律: 如图将每个月出生的兔子的编号写出来,可以发现一只兔子在哪一列他的父亲就是谁. 每列的首项可以通过菲波那契求得. 然后你就可以像我一样通过这个规律打表每个点的父亲,预处理出倍增数组,倍增 ...

  9. 【模拟8.03】斐波那契(fibonacci) (规律题)

    就是找规律,发现每个父亲和孩子的差值都是距儿子最大的fibonacc 也是可证的 f[i]表示当前月的兔子总数 f[i]=f[i-1]+f[i-2](f[i-2]是新生的,f[i-1]是旧有的) 然后 ...

随机推荐

  1. JavaScript 经常忽略的 7 个基础知识点

    1. 在 String.prototype.replace 方法中使用 /g 和 /i标志位 令很多 JavaScript 初学者意外的是,字符串的 replace 方法并不会 替换所有匹配的子串—— ...

  2. 第二个sprint第六天

    讨论地点:qq 讨论成员:邵家文.李新.朱浩龙.陈俊金 今天完成:统计功能前期工作已经完成,暂时对它进行搁置.        开发感悟:今天回了乡下吃了一顿饭,发现还是乡下环境好,比较适合在那种环境下 ...

  3. ios layoutsubView 何时被调用

    -layoutSubviews方法:这个方法,在UIView中默认没有做任何事情,需要子类进行重写.UIView是一个纯净的视图,里面没有任何子控件,所以不会做什么事情.一般系统自带控件中有子控件的都 ...

  4. PAT (Basic Level) Practise:1021. 个位数统计

    [题目链接] 给定一个k位整数N = dk-1*10k-1 + ... + d1*101 + d0 (0<=di<=9, i=0,...,k-1, dk-1>0),请编写程序统计每种 ...

  5. linux/lib/string.c

    /** * strlen - Find the length of a string * @s: The string to be sized */ size_t strlen(const char ...

  6. hdu 5187 zhx's contest

    题目分析如果n=1,答案是1,否则答案是2n−2. 证明:ai肯定是最小的或者最大的.考虑另外的数,如果它们的位置定了的话,那么整个序列是唯一的. 那么ai是最小或者最大分别有2n−1种情况,而整个序 ...

  7. [转]SQL:JOIN用法

    JOIN连接组合两个表中的字段记录,包括三种: INNER JOIN运算式:连接组合两个表中的字段记录. LEFT JOIN运算式:连接组合两个表中的字段记录,并将包含了LEFT JOIN左边表中的全 ...

  8. html--整站制作

    1.样式初置 body,div,ul,ol,h1,h2,h3,h4,h5,p,form,input,textarea,select{margin:0;padding:0;} li{list-style ...

  9. JavaWeb学习记录(十九)——开发JSTL自定义标签

    一.防盗链标签 import javax.servlet.http.HttpServletResponse;import javax.servlet.jsp.JspException;import j ...

  10. Codeforces Round #104 (Div. 1)

    A.Lucky Conversion 题意 给定两个长度为 \(N(N \le 10^5)\) 且由4和7构成的 \(a, b\)串 对 \(a\) 可以有两种操作: 交换两个位置的字符; 改变一个位 ...