Blog icon indicating copy to clipboard operation
Blog copied to clipboard

斐波那契数列的程序优化

Open pramper opened this issue 9 years ago • 0 comments

斐波那契数列数列:0 1 1 2 3 5 8 13...,用数学公式表示为:fn(n)=fn(n-1)+fn(n-2), n>1。下面我们用js程序来实现这个算,并一步步优化它。一下程序均在Node.js v6.2.0下实现、测试。

简单的JS程序

实现斐波那契数列的程序非常简单,如下:

var count = 0  // 记录函数被调用的次数
function fib(n) {
    count++
    if(n<2) {
        return n;
    }else{
        return fib(n-1) + fib(n-2);
    }
}
fib(30)  // count=2692537,耗时16.338ms

利用记忆进行优化

可以看到,上面的程序效率之差,简直不能忍。为什么会出现这种情况呢?看下面这张图:

可以看出,执行了许多相同的运算,例如fib(3)执行了两次,fib(2)执行了三次。如果我们对中间求得的变量值,进行存储的话,就会大大减少函数被调用的次数。程序如下:

var count = 0
var tmp = [0, 1] // 存储中间求得的变量值;下标即为n
function fib(n) {
    var result = tmp[n]
    count++
    if(typeof result !== 'number') {
        result = fib(n-1) + fib(n-2)
        tmp[n] = result
    }
    return result
}
fib(30) // count=59, 耗时2.531ms

这是典型的以空间换时间。很明显,函数被调用的次数大大减少,耗时明显缩减。

动态规划

其实,我觉得最好的方法还是动态规划的方法,避免了递归调用。动态规划简单来说,就是把一个问题分解成子问题,然后求子问题的解。在斐波那契数列数列中我们可以这样想,要求fib(30),我求出fib(29)和fib(28)就好;要求fib(29),我求出fib(28)和fib(27)就好;要求fib(28),我求出fib(27)和fib(26)就好...依次把问题逐渐分解最后就是求出fib(2)就好。我求出了fib(2),再求fib(3),再求fib(4)...直到求出fib(30)。程序如下:

var count = 0 // 技术循环次数
function fib(n) {
    var val = [0, 1]  // val[0]= fib[n-2], val[1]=fib[n-1]
    if(n<2) {
        return n
    }
    for(let i=2; i<n; i++) {
        count++
        let tmp = val[0] + val[1]
        val[0] = val[1]
        val[1] = tmp
    }
    return val[0] + val[1]
}
fib(30)  // count=28,耗时:2.552ms

总结

由变量count可以明显的看出使用动态规划性能最好。当n更大时,耗时也会比第二种方法少,可以自己试试。

pramper avatar Sep 10 '16 10:09 pramper