斐波那契数列(Fibonacci sequence)【思路及实现】

定义

斐波那契数列(Fibonacci sequence),又称黄金分割数列、因数学家列昂纳多·斐波那契(Leonardoda Fibonacci)以兔子繁殖为例子而引入,故又称为“兔子数列”,指的是这样一个数列:1、1、2、3、5、8、13、21、34、……在数学上,斐波纳契数列以如下被以递推的方法定义:F(1)=1,F(2)=1, F(n)=F(n-1)+F(n-2)(n>=3,n∈N*)在现代物理、准晶体结构、化学等领域,斐波纳契数列都有直接的应用。

 

实现

斐波那契数列是一种广泛应用且规律简单的线性递推数列,在学习的过程中我们常常使用递推方程的方式进行计算。其递推定义如下

F(1)=1,

F(2)=1,

F(n)=F(n-1)+F(n-2)(n>=3,n∈N*)

本文中给出如下函数接口

/**
 * @param n
 *           fibonacci数列某一项数
 * @return 
 *           fibonacci数列第n项的值
 */
public int Fibonacci(int n) {

}

本文只讨论核心的代码,对于n的合法性判断,返回值是否溢出等问题不进行考虑。但为了保证代码的健壮性希望各位读者继续对以下实现代码进行更详细的完善。

 

思路一:递归

使用递归解决该问题十分简单明了,其实现如下。

public int Fibonacci(int n) {
    if (n == 1 || n == 2)
        return 1;
    return Fibonacci(n - 1) + Fibonacci(n - 2);
}

递归这种方式十分简单,容易理解。但缺点是开销大、容易产生栈溢出。通过归纳法可以证明其渐进时间复杂度为O((5/3)^N),也即指数级,这是一种对递归十分低效的应用,其辅助空间复杂度为O(n)。另外需要注意的是这里的代码对n并未进行合法性判断,如果输入为负数将会出现栈溢出。

 

思路二:迭代

迭代算法也是基于递推方程实现,通过不断的迭代,去求解新的项。其迭代关系式如下:

X = F(0) = 0,

Y = F(1) = 1,

Y = F(1) + F(0) = F(2) ,

X = Y - X = F(2) - F(0) = F(1)

... ...

基于以上关系式,给出实现代码。

public int Fibonacci(int n) {
    int x = 0, y = 1;
    for (int i = 1; i <= n; i++) {
        y = x + y;
        x = y - x;
    }
    return x;
}

迭代思路求斐波那契数列第n项时间复杂度为O(n),辅助空间复杂度为O(1)。相比递归方式,更推荐使用迭代求解斐波那契问题。相比递归其唯一的缺点可能就是可读性没有递归方式高。

 

思路三:数组

以上两种思路是学习中较为常见的求解斐波那契数列第n项的方式,但如果问题变化,变成求解斐波那契数列前n项,继续使用以上思路的时间复杂度就会变为O(N(5/3)^N)和O(N^2),这是相对复杂的。所以在斐波那契数列前n项时,推荐利用数组来解决问题。其实现代码如下。

public int[] Fibonacci(int n) {
    int fib[] = new int[n+1];
    fib[0] = 0;
    fib[1] = 1;
    for (int i = 2; i <= n; i++) {
        fib[i] = fib[i - 1] + fib[i - 2];
    }
    return fib;
}

数组实现类似于前文的递归实现,利用前两项元素之和去求解第三项。所以时间复杂度是O(n)。这种方式的优点在于,每一项的生成都依赖于数组中现有的项,所以如果得到了第n项,也就得到了前n-1项。产生的所有的中间结果都保存在数组之中,如果使用上文的迭代方式求解斐波那契数列前n项,每次生成新项都需要从F(1)开始重新进行迭代,效率较低。

还需要注意的是,由于使用了int类型的数组,所以n的值最大只能为46,因为F(46) = 1836311903。再向上进行迭代就会造成int类型溢出。对于这个问题可以采用long或者BigInteger去解决。

 

思路四:通项公式

由于斐波那契数列是一个线性递推数列,所以可以使用多种方法(特征方程、待定系数、blabla...)去推导,最终得到的通项公式如下:

代码实现如下。

public int Fibonacci(int n) {
    final double SQRT_FIVE = Math.sqrt(5);
    double result = 1 / SQRT_FIVE * (Math.pow((1 + SQRT_FIVE) / 2, n) - Math.pow((1 - SQRT_FIVE) / 2, n));
    return (int) result;
}

对于求解斐波那契数列某一项而言,这种方式的渐进时间复杂度最低——O(1),因为是利用公式。然鹅如果不无视pow的开销的话,复杂度大概是O(logN),因为Java的Math.pow()底层其实是一个native方法StrictMath.pow(),调用了C++中的pow函数,其渐进时间复杂度为O(logN)。

其实现思路算法大致如下:

public double pow(double x, int y){  
    if (y == 0) return 1; 
    if (y == 1) return x;  
    double result = 0;  
    double tmp = pow(x, y/2);  
    if(y & 1 != 0){  //判断奇偶
        result = x * tmp * tmp;  
    } else{  
        result = tmp * tmp;  
    }  
    return result;  
}  

熟悉的同学应该能看出来这就是快速幂的一种实现哈哈哈~~~


以上是笔者关于解决斐波那契数列问题的几种思路,代码还有许多不完善的地方,如有缺漏,欢迎大家指正。

  • 29
    点赞
  • 105
    收藏
    觉得还不错? 一键收藏
  • 13
    评论
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值