初窥门径 - 原来递归斐波那契如此慢

初入算法的人都会听过斐波那契数列,看看维基百科的定义:

斐波那契数列(意大利语 Successione di Fibonacci),又译为费波拿契数斐波那契数列费氏数列黄金分割数列
在数学上,费波那契数列是以递归的方法来定义:

初窥门径 - 原来递归斐波那契如此慢_第1张图片

用文字来说,就是费波那契数列由0和1开始,之后的费波那契系数就是由之前的两数相加而得出。首几个费波那契系数是:
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233……

如果以编程方式来获取第 N 个数的值,我很自然的就想到递归

// in javascript
function fibonacci(n){
    if(n <= 0) return 0;
    if(n == 1) return 1;
    return fibonacci(n-1) + fibonacci(n-2);
}

看着是不是很简洁? 嗯,很标准,边界确定了,也有一步步递进的求解过程,那我们放在浏览器 console 上跑一跑,顺便看看运行时间,为此再来帮助函数

function time(fn, ...args) {
    var start = new Date().valueOf();
    console.log(fn(...args))
    var end = new Date().valueOf();
    console.log('elapsed: ' + (end - start) + 'ms');
}

先来获取 n = 10 的值,看下面的结果正常,1毫秒一开始也怎么在意。


初窥门径 - 原来递归斐波那契如此慢_第2张图片

那再来试试 n = 50的... 等了好一会 还没打印出来结果 还以为浏览器卡了!!!


初窥门径 - 原来递归斐波那契如此慢_第3张图片

这才到50, 就花去了330秒,5分多钟了喂,真可怕 !?(・_・;? 连三位数都还没到,更别说w级别以上了。

为毛会这么慢??? 我们以 n = 10 来分析下过程

初窥门径 - 原来递归斐波那契如此慢_第4张图片

先原谅我这丑不拉几的字迹... 沿着这个树形结构往回推进,发现Fib(8) 执行了2次,Fib(7)执行了3次,Fib(6)执行3次以上,存在着大量重复的计算,复杂度达到指数级别,而且数量级越大重复越严重。这二叉树没有满,我们按满的算那总的计算次数为 T(N) = 1 + 2 + 4 + 8 + ... + 2^10 => 2^n+1 -1 大概时间复杂度是 O( (2^N), 最牛逼的那个N次方。

换个思路

前面的递归是从第n位往回推导的,那我们正向的去一步步得出相应位置的值,就可以省去反向递归带来的大量重复操作了,也就是常说的递推,时间复杂度为 O(N) 代码如下:

// 递推
function fibonacci(n) {
    if (n <= 0) return 0;
    if (n == 1) return 1;

    var first = 0,
        second = 1,
        index = 1,
        fib = 0;

    while (index++ < n) {
        fib = first + second;
        first = second;
        second = fib;
    }
    return fib;
}

试验下, 50 几乎不耗时

初窥门径 - 原来递归斐波那契如此慢_第5张图片

慢慢往上加, 可以看到到1w的时候 js的数字已经装不下了, 但是获取第10w位置的也才需要2ms的时间。


初窥门径 - 原来递归斐波那契如此慢_第6张图片

补充@林晓池Smile 评论的说法, 以空间换时间,利用数组来存储已经计算过的斐波那契数列中的值

var memo = [];
function fibonacci(n){
    if(n <= 0) return 0;
    if(n == 1) return 1;
    if(!memo[n])
        memo[n] = fibonacci(n-1) + fibonacci(n-2);
    return memo[n];
}

测试看看


初窥门径 - 原来递归斐波那契如此慢_第7张图片

可以看到相比一开始的递归,省去了很多重复的计算;但是获取第10w个数的时候堆栈就溢出了,因为即使记忆了但是还存在递归调用, 递推的方案倒是不会
调用堆栈的上限如下:
Three results:
Node.js: 11034
Firefox: 50994
Chrome: 10402

入门例子就可以体会到合理选择算法的威力了,我要继续努力~ 为分布式系统节省机器

网上对斐波那契有其他更优质的算法,但是那种数学公司俺看不懂。。。 以后再回炉重造学习高数吧。

你可能感兴趣的:(初窥门径 - 原来递归斐波那契如此慢)