斐波纳契数列(Fibonacci Sequence)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/mobius_strip/article/details/8222309

斐波纳契数列(Fibonacci Sequence 


0.前言

很久以前就想写一些竞赛学习的总结,但是由于之前事情比较多,导致计划不断的减缓。现在,大学教学任务的考试已经全部结束了,而比赛也告一段落,所以有时间来整理一下之前学过的东西。不久前,在做比赛的时候遇到了这样一个问题:求出第N斐波纳契数的前M位和后K位。所以就将斐波纳契数列(Fibonacci Sequence作为第一步吧。(后面我简称斐波那契数列为Fib,函数Fib(x),代表第x个斐波那契数)

1.从兔子说起

问题:一般而言,兔子在出生两个月后,就有繁殖能力,一对兔子每个月能生出一对小兔子来。如果所有兔都不死,那么一年以后可以繁殖多少对兔子?

    解: 幼仔对数 = 前月成兔对数  

            成兔对数 = 前月成兔对数+前月幼仔对数  

            总体对数 = 本月成兔对数+本月幼仔对数 

            得到通向公式:an+2 = an+1 + ana1 = a2 = 1

    这就是Fib的由来和递推公式的计算。

2.Fib的计算1:递推公式

利用递推公式:Fib(n+2) = Fib(n+1) + Fib(n)Fib(1) = Fib(2) = 1

可以直接想到最简单的计算方法,递归运算。

代码:

int Fib( n )
{
    if ( n == 1 || n == 2 ) return 1;
    else return Fib(n-1) + Fib(n-2);
}

分析:递归运算的Fib的时间复杂性为O(Fib(N))

证明:设TFib(n) 为计算Fib(n)的运算次数,记TFib(1) = TFib(2) =O(1)

           当n = 1时有,TFib(1) = O( Fib(1) ) 结论成立。

           假设当n=k时结论成立,那么有TFib(k) = O( Fib(k) )TFib(k-1)=O( Fib(k-1) )

           当n=k+1时有,TFib(k+1) = O( Fib(k) )+ O( Fib(k-1) ) + O(1) = O( Fib(k+1) ),成立。

           综上所述,结论成立。

说明:看完后面的通向公式,我们可以知道,这是一个指数级的算法。

3.Fib的计算2:动态规划

利用动态规划的思想:Fib[ 1 ] = Fib[ 2 ] = 1Fib[ n ] = Fib[ n-2 ] + Fib[ n-1 ]

这个式子看起来和上面的很相似,但是却又很大的区别。我们发现在计算Fib(n-1)Fib(n-2)又被计算了一次,所以导致大量的重复计算,为了避免重复计算,我们可以将之前计算出来的结果储存起来,这就是动态规划的思想了。

代码:

int Fib( int n ) 
{
    int F[ MAXSIZE ];
    F[ 1 ] = F[ 2 ] = 1;
    for ( int i = 3 ; i <= n ; ++ i )
        F[ i ] = F[ i-2 ] + F[ i-1 ];
    return F[ n ];
}

分析:动态规划算法的时间复杂性为O(n)

说明:这个可以很简单的看出来,因为每个Fib只求解了一次。这是一个线性的算法。

4.Fib的计算3:分治法

a.快速幂:

这里先要说明一下快速幂的算法,即计算x^n的对数级算法。

其实这个很像前面的动态规划的思想,计算x^n,可以用x^(n/2) * x^(n/2) * K来计算,其中当n%2 = 1K=x否则K=1。所以这个算法的运行就和二分查找相似了。

代码:

int qpow( int x, int n )
{
    if ( n == 1 ) return x;
    int v = qpow( x, n/2 );
    if ( n%2 ) return v*v*x;
    else return v*v; 
}

分析:快速幂的时间复杂性为O(logN),这是一个对数阶算法。

说明:由于x^(n/2)只需要被计算一次就行,所以计算的过程就是,n -> n/2 -> n/4 -> ... ->1所以计算logN次。

           这里也可以用递归式证明,T(n) = T(n/2) + O(1) => T(n) = O(logN)

b.Fib的矩阵表示:

Jn为第n个月有生育能力的兔子数量,An为这一月份的兔子数量。得到如下递推矩阵。

 其中 

这个可以用数学归纳法简单的证明,这里就不做证明。

然后我们把上面的快速幂算法应用到矩阵中,就得到了一个对数级的Fib算法。

代码:

/* 矩阵快速幂,其中结果在Bas中 */
#define SIZE 2
#define MOD 10000007

long long Mat[ SIZE ][ SIZE ];
long long Bas[ SIZE ][ SIZE ];
long long Add[ SIZE ][ SIZE ];

/* 清零函数 */
void CLEAR( long long A[][ SIZE ], int m )
{
	for ( int i = 1 ; i <= m ; ++ i )
	for ( int j = 1 ; j <= m ; ++ j )
		A[ i ][ j ] = 0LL;
}

/* 矩阵乘法 */
void MUL( long long A[][ SIZE ], long long B[][ SIZE ], long long C[][ SIZE ], int m ) 
{
	CLEAR( A, m );
	for ( int i = 1 ; i <= m ; ++ i )
	for ( int j = 1 ; j <= m ; ++ j )
	for ( int k = 1 ; k <= m ; ++ k )
		A[ i ][ j ] = (A[ i ][ j ]+B[ i ][ k ]*C[ k ][ j ])%MOD;
}

/* 矩阵复制 */
void COPY( long long A[][ SIZE ], long long B[][ SIZE ], int m )
{
	for ( int i = 1 ; i <= m ; ++ i )
	for ( int j = 1 ; j <= m ; ++ j )
		A[ i ][ j ] = B[ i ][ j ];
}

/* 矩阵快速幂 */
void POW( long long n, int m )
{
	if ( n == 1LL ) 
		COPY( Bas, Mat, m );
	else {
		POW( n/2LL, m );
		COPY( Add, Bas, m );
		MUL( Bas, Add, Add, m );
		if ( n%2LL ) {
			COPY( Add, Bas, m );
			MUL( Bas, Add, Mat, m );
		}
	}
}

说明:这里可以利用取模运算计算出Fib(n)的后k位。

5.Fib的计算4:通向公式

如果我们知道一个数列的通向,那么求解这个数列就会容易很多。

计算Fib的通向的方法有很多,这里直接给出结论:


其正确性可以通过数学归纳法证明。

这里我们又回到了快速幂的计算上来了。

代码:

double qpow( int x, int n )
{
    if ( n == 1 ) return x;
    double v = qpow( x, n/2 );
    if ( n%2 ) return v*v*x;
    else return v*v; 
}

分析:时间复杂性为O(logN)

说明:这里可以利用这个方法除以10^m 计算出Fib的前k位。

6.FIb恒等式

这里给出一些与Fib有关的恒等式,有可能会用到,但不做证明。(证明可使用数学归纳法)

A.F1 + F2 + F3 + ... + Fn = Fn+2 − 1
B.F1 + 2F2 + 3F3 + ... + nFn = nFn+2 − Fn+3 + 2
C.F1 + F3 + F5 + ... + F2n−1 = F2n
D.F2 + F4 + F6 + ... + F2n = F2n+1 − 1
E.(F1)^2 + (F2)^2 + (F3)^2 + ... + (Fn)^2 = FnFn+1
F.Fn-1Fn+1 - (Fn)^2 = (-1)^n

7.神奇的Fib

计算告一段落了,最后做一下Fib的宣传工作。

说道Fib的最神奇的地方就是它与黄金分割的关系了:

这个可以用通向公式简单的证明。在做二分查找的时候可以利用黄金比例分为而不是对半分割,可能会效率更高。

自然界中对于黄金分割的诠释无处不在,最后在这里贴几张图片来感受一下他的神奇:

 

这两张图片是从下面的图片中抽象出来的:




猜你喜欢

转载自blog.csdn.net/mobius_strip/article/details/8222309