Fibonacci数列定义

Fibonacci数列的通常定义

定义一:

\[fib(n)=
\begin{cases}
n\quad\quad\quad\quad\quad\quad\quad\quad\quad\quad\quad\quad(若n<=1)\\
fib(n-1)+fib(n-2)\quad\quad\quad(若n>=2)
\end{cases}
\]

由这个定义,可以得到

下标n 0 1 2 3 4 5 6 7 8 9 …….
fib(n) 0 1 1 2 3 5 8 13 21 34 ……..

即:fib(0) = 0, fib(1) = 1, fib(2) = 1, fib(3) = 2 …………

在另外一些书上,Fibonacci数列是这样定义的

定义二:

\[fib(n)=
\begin{cases}
1\quad\quad\quad\quad\quad\quad\quad\quad\quad\quad\quad\quad(若n=0,1)\\
fib(n-1)+fib(n-2)\quad\quad\quad(若n>=2)
\end{cases}
\]

同样由这个定义,可以得到

下标n 0 1 2 3 4 5 6 7 8 9 …….
fib(n) 1 1 2 3 5 8 13 21 34 55 ……..

定义二和定义一对 f(0)的初始值不一样,定义一中 f(0) = 0,定义二中f(0) = 1,所以定义一相比定义二要推迟1个单位。但是作为Fabonacci它们基本的核心定义是一样的也即 fib(n) = fib(n-1) + fib(n-2)


下面都是基于【 定义一】 来做的讨论,保持与邓俊辉版本的《数据结构》一致

二分递归

根据定义可以知道,Fibonacci数列实际上是一个递归定义,要计算f(n)就要知道f(n-1)和f(n-2),要计算f(n-1),就要知道f(n-2)和f(n-3),以此类推,而边界就是我们一开始给定的 fib(0) = 0, fib(1) = 1,

以fib(6)为例,下面是计算fib(6)展开的递归树

graph TD
C[fib6]
C–>D[fib5]
C –>E[fib4]
D–>F[fib4]
D–>G[fib3]
E–>H[fib3]
E–>I[fib2]
F–>F1[fib3]
F–>F2[fib2]
G–>G1[fib2]
G–>G2[fib1]
H–>H1[fib2]
H–>H2[fib1]
I–>I1[fib1]
I–>I2[fib0]
F1–>F11[fib2]
F1–>F12[fib1]
F11–>F111[fib1]
F11–>F112[fib0]

F2–>F21[fib1]
F2–>F22[fib0]

G1–>G11[fib1]
G1–>G12[fib0]

H1–>H11[fib1]
H1–>H12[fib0]

由此可以得到计算Fibonacci数的递归代码:

int fib(int n)
{
	if(2 > n)
       return n;   //递归基
     else
        return fib(n-1) + fib(n-2);    //递推公式
}

这是一个二分递归,从n开始,计算每一个fib(n)时都要分支为两个计算,故 时间复杂度为 O(2^n)

空间复杂度线性正比于递归深度,故这里的空间复杂度为O(n)

二分递归在计算过程中所出现的递归实例的重复度极高,如上面计算fib6时,fib4重复出现了2次,fib3重复出现了3次,fib2重复出现了5次,这是致使二分递归时间复杂度达到指数级别的真正原因所在。

——更多优化

1.线性递归

线性递归版本一:

Fibonacci数在计算过程中每一步并不是相互独立的,可以用到上一次的计算结果。那么考虑把中间先计算出的结果保存下来,如果当再次需要这个结果的时候先检查是否已经得到了。

int dp[MAXN];   //dp[n] 表示 fib(n)的结果
fill(dp,dp+MAXN,-1); //初始化dp[]的每个值为-1,dp[n] = -1表示F(n)当前还没有被计算过
int fibL(int n)
{
   if(2 > n)
      return n;
      
   if(dp[n] != -1)
   {
   	   return dp[n];
   }
    else
    {
    	dp[n] = fibL(n-1) + fibL(n-2);
        return dp[n];
    }
}
//《算法笔记》上的版本

这个版本的fibonacci数计算也用到了动态规划思想的方法,也即是在计算过程中记录子问题的解,来避免下次遇到相同的子问题时重复计算。这也称之为记忆搜索时间复杂度和空间复杂度均为O(n)

与二分递归的递归计算图相比,下面是Fibonacci数列记忆搜索示意图

graph TD
C[fib6]
C–>D[fib5]
C –>E[fib4]
D–>F[fib4]
D–>G[fib3]
F–>F1[fib3]
F–>F2[fib2]
F1–>F11[fib2]
F1–>F12[fib1]
F11–>F111[fib1]
F11–>F112[fib0]

​ 图:Fibonacci数列记忆搜索示意图

线性递归版本二:

/*
函数说明:
prev作为辅助变量记录前一项,当前函数返回的是数列当前项
*/
int fibL(int n,int& prev)
{
	//递归基,直接取值
	if(0 == n)
	{
		prev = 1; //相当于初始 fib(-1) = 1 
		return 0;  // fib(0) = 1
	}
	else //深入递归计算
	{
		int prePrev;
		prev = fibL(n-1,prePrev);//递归计算前两项
		return prevPrev + prev;//其和即为正解
	}
}
//邓俊辉《数据结构》上的版本

/*
使用例子:
int prev;
fibL(6,prev);
结果:
fibL(6,prev) = 8;
prev = 5;
*/

这个版本稍微难理解点,主要是借助一个参数引用来一直记录前一项的值 。

和 fib(n) = fib(n-1) + fib(n-2) 相比,这里省略了 fib(n-2),而实际上 fib(n-2)的解答在这里借助了形式参数的机制,通过变量 prevPrev “调阅” 此前的记录直接获得。

时间复杂度和空间复杂度均为O(n)

2.迭代(动态规划的思想)

​ 如果从 1->n 计算 fib(n)

​ fib0 + fib1 = fib2 —> fib1 +fib2 = fib3 —> fib2 + fib3 = fib4 —> fib3 + fib4 = fib5 ………….

由此可以得到迭代版代码:

int fibI(n)
{
	int f = 1,g =0; //相当于fib(-1) = 1,fib(0) = 0;
	while(n--)
	{
		g = g + f;  //g 变为g与f的和,也即g往前进一步
		f = g - f;  // f变为g,也即f往前进一步
	}
	return g;
}

不难得出时间复杂度为O(n),空间复杂度为 O(1)

3.快速幂矩阵求斐波那契数列

还有一种更快的方法求斐波那契数列的方法,时间复杂度仅为:O( logn ),就是马上要介绍的斐波那契数列的快速幂矩阵方法

首先,由矩阵乘法,我们知道下式成立

\[\begin{pmatrix}
\mathbf{0} & \mathbf{1} \\
\mathbf{1} & \mathbf{1} \\
\end{pmatrix}
\times
\begin{pmatrix}
\mathbf{fib(k-1)} \\
\mathbf{fib(k)} \\
\end{pmatrix}
=
\begin{pmatrix}
\mathbf{fib(k)} \\
\mathbf{fib(k-1)+fib(k)} \\
\end{pmatrix}
=
\begin{pmatrix}
\mathbf{fib(k)} \\
\mathbf{fib(k+1)} \\
\end{pmatrix}
\]

也即

\[\begin{pmatrix}
\mathbf{fib(k)} \\
\mathbf{fib(k+1)} \\
\end{pmatrix}
=
\begin{pmatrix}
\mathbf{0} & \mathbf{1} \\
\mathbf{1} & \mathbf{1} \\
\end{pmatrix}
\times
\begin{pmatrix}
\mathbf{fib(k-1)} \\
\mathbf{fib(k)} \\
\end{pmatrix}
\]

由此,可以进一步推导如下通项公式:

\[\begin{pmatrix}
\mathbf{fib(n)} \\
\mathbf{fib(n+1)} \\
\end{pmatrix}
=
\begin{pmatrix}
\mathbf{0} & \mathbf{1} \\
\mathbf{1} & \mathbf{1} \\
\end{pmatrix}
^n
\times
\begin{pmatrix}
\mathbf{fib(0)} \\
\mathbf{fib(1)} \\
\end{pmatrix}
=
\begin{pmatrix}
\mathbf{0} & \mathbf{1} \\
\mathbf{1} & \mathbf{1} \\
\end{pmatrix}
^n
\times
\begin{pmatrix}
\mathbf{0} \\
\mathbf{1} \\
\end{pmatrix}
\]

\[设:
\begin{pmatrix}
\mathbf{0} & \mathbf{1} \\
\mathbf{1} & \mathbf{1} \\
\end{pmatrix}
^n
=
\begin{pmatrix}
\mathbf{a} & \mathbf{b} \\
\mathbf{c} & \mathbf{d} \\
\end{pmatrix}


\begin{pmatrix}
\mathbf{fib(n)} \\
\mathbf{fib(n+1)} \\
\end{pmatrix}
=
\begin{pmatrix}
\mathbf{a} & \mathbf{b} \\
\mathbf{c} & \mathbf{d} \\
\end{pmatrix}
\times
\begin{pmatrix}
\mathbf{0} \\
\mathbf{1} \\
\end{pmatrix}
=
\begin{pmatrix}
\mathbf{b} \\
\mathbf{d} \\
\end{pmatrix}
\]

由此可以得到 fib(n) = b, fib(n+1) = d,

其中

\[\begin{pmatrix}
\mathbf{0} & \mathbf{1} \\
\mathbf{1} & \mathbf{1} \\
\end{pmatrix}
^n
\quad,矩阵乘法的计算,可以套用快速幂的算法流程,快速幂的时间复杂度仅为O(logn)
\]

将普通数的两个数的乘法运算换成两个矩阵的乘法运算,即可实现 fib(n) 的计算。更重要的是。这里仅涉及

2 × 2 的矩阵的计算,每次同样只需要常数的时间,故整体的运行时间也是 O ( logn )

矩阵乘法

\[\begin{pmatrix}
\mathbf{a}_{11} & \mathbf{a}_{12} \\
\mathbf{a}_{21} & \mathbf{a}_{22} \\
\end{pmatrix}
\times
\begin{pmatrix}
\mathbf{b}_{11} & \mathbf{b}_{12} \\
\mathbf{b}_{21} & \mathbf{b}_{22} \\
\end{pmatrix}
=
\begin{pmatrix}
\mathbf{a}_{11}\times{b}_{11}+{a}_{12}\times{b}_{21}&
\mathbf{a}_{11}\times{b}_{12}+{a}_{12}\times{b}_{22} \\
\mathbf{a}_{21}\times{b}_{11}+{a}_{22}\times{b}_{21} &
\mathbf{a}_{21}\times{b}_{12}+{a}_{22}\times{b}_{22} \\
\end{pmatrix}
\]

示例代码:

#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std;

//  2*2 的矩阵 
struct mat{
    int res[2][2];
    //构造函数 
    mat()
    {
        memset(res,0,sizeof(res));
    }
    //构造函数 
    mat(int a00,int a01,int a10,int a11)
    {
        res[0][0] = a00;
        res[0][1] = a01;
        res[1][0] = a10;
        res[1][1] = a11;
    }
    //深度拷贝方法 
    mat operator=(const mat& a)
    {
        for(int i=0;i<2;i++)
            for(int j=0;j<2;j++)
                res[i][j] = a.res[i][j];
        return *this;
    }
};

/*
  矩阵乘法,n维矩阵的相乘 
*/ 
mat multi(mat a,mat b,int n)
{
    mat c;
    for(int i=0;i<n;i++)//行
    {
        for(int j=0;j<n;j++)//列
        {
            for(int k=0;k<n;k++)//枚举个数
            {
                c.res[i][j]=(c.res[i][j]+a.res[i][k]*b.res[k][j]);
            }
        }
    }
    return c;
}

/*
   矩阵乘法快速幂
  ( 0 1 ) ^n
  ( 1 1 )
*/
mat matirix_pow(mat a,int n)
{
    mat pow(1,0,0,1);//单位矩阵,相当于1
    mat p = a;
    while(0 < n)
    {
        if(n&1)
            pow = multi(pow,p,2);    // pow = pow*p
        p = multi(p,p,2);   // p = p*p
        n >>= 1;   //  n = n/2
    }
    return pow;
}

/*
	用快速矩阵乘法求fibonacci数列 
	  fib(n) =  fibMat.res[0][1];
	fib(n+1) =  fibMat.res[1][1];
*/ 
int fib_matrixPow(int n)
{
	mat a(0,1,1,1);
	mat fibMat = matirix_pow(a,n);
	return fibMat.res[0][1];
} 

int main()
{
	cout<<"fib(9) = "<<fib_matrixPow(9)<<endl;
    return 0;
}

版权声明:本文为understandWorld原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/understandWorld/articles/11218657.html