斐波那契数列算法

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了斐波那契数列算法相关的知识,希望对你有一定的参考价值。

参考技术A 斐波那契数列,又称黄金分割数列,指的是这样一个数列:0、1、1、2、3、5、8、13、21、34、……在数学上,斐波纳契数列以如下被以递归的方法定义:F(0)=0,F(1)=1,F(n)=F(n-1)+F(n-2)(n≥2,n∈N*)在现代物理、准晶体结构、化学等领域,斐波纳契数列都有直接的应用,为此,美国数学会从1963起出版了以《斐波纳契数列季刊》为名的一份数学杂志,用于专门刊载这方面的研究成果。

我们以求解f(10)作为例子来分析递归求解的过程。要求得f(10),需要求得f(9)和f(8)。同样,要求得f(9),要先求得f(8)和f(7)……我们用树形结构来表示这种依赖关系

在这棵树中有很多结点会重复的,而且重复的结点数会随着n的增大而急剧增加。这意味这计算量会随着n的增大而急剧增大。事实上,用递归方法计算的时间复杂度是以n的指数的方式递增的,时间复杂度约等于O(2^n)

空间复杂度分析

在方法一的基础上改进

其实改进的方法并不复杂。上述方法之所以慢是因为重复的计算太多,只要避免重复计算就行了。比如我们可以把已经得到的数列中间项保存起来,如果下次需要计算的时候我们先查找一下,如果前面已经计算过了就不用再次计算了。算法步骤如下

一目了然,O(1) 约等于O(1)

这个是我目前见过最流弊的方式,但是前提你得先会用公式
时间复杂度是O(logn),就问你流弊不流弊。在介绍这种方法之前,先介绍一个数学公式:

算法实验课-1:斐波那契数列

算法实验课-1:斐波那契数列

1.定义

斐波纳契数列(Fibonacci sequence)以递归的方法定义:F(0)=0,F(1)=1, F(n)=F(n-1)+F(n-2)(n>=2,n∈N*)

2.求斐波那契数列的算法

2.1递归算法

根据定义实现的算法, 简单易实现

int Fib_Recursive(int n)
{
    if(n == 0)
        return 0;
    else if(n == 1)
        return 1;
    else if(n == 2)
        return 1;
    else
        return Fib_Recursive(n-1)+Fib_Recursive(n-2);
}

当要求的第n位稍大时, 该算法计算量急剧增大, 用递归方法计算的时间复杂度是以n的指数的方式递增的, 时间复杂度为O(2^n).

2.2迭代算法

定义一个数组 Arr[3] = { 0, 1, 1 } , 当求n = 0 , 1 , 2 时,直接从数组中返回Arr[n].

在n >= 3时,对数组中的数据进行迭代循环来计算第n个斐波那契数列. 例如, 在求第3个斐波那契数时:

Arr[0] <- Arr[1];			//	Arr[0] = 1
Arr[1] <- Arr[2];			//	Arr[1] = 1

Arr[2] <- Arr[0] + Arr[1];
//	Arr[2] = 2, 此时Arr[2]就是第三个斐波那契数
//	如果要求第4个, 就循环这种步骤两次

代码如下:

int Fib_Iteration(int n)
{
    int FibArray[3] = {0,1,1};
    if(n < 3)
        return FibArray[n];
    else
    {
        //从i= 3开始循环
        for(int i = 3; i <= n; ++i)
        {
            FibArray[0] = FibArray[1];
            FibArray[1] = FibArray[2];
            FibArray[2] = FibArray[0] + FibArray[1];
        }
        return FibArray[2];
    }
}

单层循环,时间复杂度为O(n)

2.3通项公式法

斐波那契数列的通项公式如下:

double Fib_Formula(int n)
{
    double phi = (1 + sqrt(5))/2;
    double Fib_n = pow(phi,n) / sqrt(5);
    Fib_n = floor(Fib_n + 0.5);
    return Fib_n;
}
//由于各个函数返回值是double型,所以最后取整会有小数部分的0

在我的机器上如果返回float类型的值,由于精度的差异,最后一个准确的值是第31个数.

2.4矩阵乘方计算

这个算法基于上述公式 , 每次计算只需要对整数进行操作 , 是一个计算斐波那契数列的高效方法.

当以上公式中的 n = 1 时 , 可以在矩阵中找到 F(0) , F(1) , F(2)的值

2.4.1迭代计算矩阵乘方

/* 矩阵乘方 一 */
unsigned int Fib_Matrix(int n)
{
    //用二维数保存矩阵初始状态
    unsigned int FibMatrix[2][2] = {{0,1},{1,1}};
    if(n == 0)
        return  FibMatrix[0][0];
    else if(n == 1)
        return FibMatrix[0][1];
    else
    {
        //数组arr保存每次进行矩阵相乘后的结果
        unsigned int arr[2][2];
        //循环计算矩阵乘方
        for(int i = 2; i <= n; ++i)
        {
            arr[0][0] = FibMatrix[0][0] * 0 + FibMatrix[0][1] * 1;
            arr[0][1] = FibMatrix[0][0] * 1 + FibMatrix[0][1] * 1;
            arr[1][0] = FibMatrix[1][0] * 0 + FibMatrix[1][1] * 1;
            arr[1][1] = FibMatrix[1][0] * 1 + FibMatrix[1 ][1] * 1;

            //将arr中的结果赋给FibMatrix, 用于下一次矩阵乘法
            for(int j = 0; j < 2; ++j)
            {
                for(int k = 0; k < 2; ++k)
                     FibMatrix[j][k] = arr[j][k];
            }
        }
        return FibMatrix[0][1];
    }
}

/* 此种方式在计算矩阵的n次幂 (n >= 2) 时,要进行 n-1 次循环运算
   所以采用分治法计算矩阵的乘方
*/

2.4.2分治法计算矩阵乘方

要计算 a^n , 有以下两种情况

  1. n 为偶数时 , 有:

    再继续判断 n/2 的奇偶性;

  2. n 为奇数时 , 有:

    再继续判断(n-1)/2 的奇偶性;

  3. 直到判断到 n/2 或 (n-1)/2 等于1时 , 开始回溯计算矩阵乘方 , 这样求幂运算的复杂度为O(log n)

/* 矩阵乘方 二 */
//这里的递归return的 值 在运算中没有实际作用, 
//传入的第一个参数 数组名 相当于传地址调用
//可以将矩阵乘方的结果带回上一层递归

//计算矩阵乘方
unsigned int Matrix_Pow(unsigned int arr[2][2], int n)
{
    if(n == 1)
    {
        arr[0][0] = 0;
        arr[0][1] = 1;
        arr[1][0] = 1;
        arr[1][1] = 1;
        return 0;
    }
    else
    {
        //偶数情况
        if(n % 2 == 0)
        {
            Matrix_Pow(arr, n/2);
            //临时保存矩阵相乘结果
            unsigned int temp[2][2];
         	//这里的计算过程应该再封装一下, 代码就不会那么恶心了,跟baba一样
            temp[0][0] = arr[0][0] * arr[0][0] + arr[0][1] * arr[1][0];
            temp[0][1] = arr[0][0] * arr[0][1] + arr[0][1] * arr[1][1];
            temp[1][0] = arr[1][0] * arr[0][0] + arr[1][1] * arr[1][0];
            temp[1][1] = arr[1][0] * arr[0][1] + arr[1][1] * arr[1][1];
            //重新赋值给arr数组
            for(int j = 0; j < 2; ++j)
            {
                for(int k = 0; k < 2; ++k)
                    arr[j][k] = temp[j][k];
            }
            return 1;
        }
        else
        {
            Matrix_Pow(arr, (n-1)/2);
            unsigned int temp[2][2];
            temp[0][0] = arr[0][0] * arr[0][0] + arr[0][1] * arr[1][0];
            temp[0][1] = arr[0][0] * arr[0][1] + arr[0][1] * arr[1][1];
            temp[1][0] = arr[1][0] * arr[0][0] + arr[1][1] * arr[1][0];
            temp[1][1] = arr[1][0] * arr[0][1] + arr[1][1] * arr[1][1];

            //奇数情况矩阵相乘后,还要再次与初始矩阵相乘
            arr[0][0] = temp[0][0] * 0 + temp[0][1] * 1;
            arr[0][1] = temp[0][0] * 1 + temp[0][1] * 1;
            arr[1][0] = temp[1][0] * 0 + temp[1][1] * 1;
            arr[1][1] = temp[1][0] * 1 + temp[1][1] * 1;

            return 2;
        }
    }
}

//调用分支计算矩阵函数, 得到最终结果
unsigned int Fib_Matrix(int n)
{
     //二维数组表示矩阵初始状态
    unsigned int FibMatrix[2][2] = {{0,1},{1,1}};
    if(n == 0)
        return FibMatrix[0][0];
    else if(n == 1)
        return FibMatrix[0][1];
    else
    {
        //将数组 arr 作为参数传入计算矩阵乘方的函数
        unsigned int arr[2][2];
        Matrix_Pow(arr,n);
        return arr[0][1];
    }
}

3.总结

整体来说这几种方法只要知道定义或者相关公式都还是比较好实现, 还有本次实验课的一些花里胡哨的要求就不记录了,还有就是矩阵那段乘法不封装是真的难看, 不过就这样吧

以上是关于斐波那契数列算法的主要内容,如果未能解决你的问题,请参考以下文章

递归求斐波那契数列

斐波那契数列

介绍下斐波那契数列。

斐波那契数列(递归非递归算法)

用递归法计算斐波那契数列的第n项

Python算法三种斐波那契数列算法