由斐波那契数列所引发的性能优化

Fibonacci 数列:一个 Fibonacci 数字是之前两个 Fibonacci 数字之和,最前面的两个数字是 0 和 1 。

很显然要用一个递归函数来实现:

    var opt = {};
    opt.nLoop = 0;
    function fib(n){
        opt.nLoop++;
        return n < 2 ? n : fib(n-2) + fib(n-1);    
    }
    
    for(var i = 0; i <= 10; i++){
        log('<br>' + i + ':' +fib(i) + '-----------调用了fib函数 '  + opt.nLoop + ' 次');
    } 

咋一看,没什么问题,但打印出的结果:

0:0  -----------调用了fib函数 1 次
1:1  -----------调用了fib函数 2 次
2:1  -----------调用了fib函数 5 次
3:2  -----------调用了fib函数 10 次
4:3  -----------调用了fib函数 19 次
5:5  -----------调用了fib函数 34 次
6:8  -----------调用了fib函数 59 次
7:13 -----------调用了fib函数 100 次
8:21 -----------调用了fib函数 167 次
9:34 -----------调用了fib函数 276 次
10:55-----------调用了fib函数 453 次

再看代码,我们只在 for 循环里调用了 fib 函数 11 次,而它自身却调用了自己 442 次。如果让该函数具有记忆功能,就可显著地减少运算量。在 js 里用对象或数组实现这种优化是非常方便的。

我们用一个名为 memo 的数组里保存我们的存储结果,存储结果隐藏在闭包中。当函数被调用时,首先检查结果是否存在,如存在就立即返回它。

    opt.nMemo  = 0;
    //设置函数记忆
    var fibonacci = function (){
        var memo = [0,1];
        var fib = function(n){
            opt.nMemo++;
            var result = memo[n];
            if( typeof result !== 'number'){
                result = fib(n-2) + fib(n-1);
                //存储结果
                memo[n] = result;
            }
            
            return result;
        }
        
        return fib;
    }();
    
    
    for(var i = 0; i <= 10; i++){
        log('<br>' + i + ':' +fibonacci(i) + '-----------调用了fib函数 '  + opt.nMemo + ' 次');
    }

查看结果

0:0  -----------调用了fib函数 1 次
1:1  -----------调用了fib函数 2 次
2:1  -----------调用了fib函数 5 次
3:2  -----------调用了fib函数 8 次
4:3  -----------调用了fib函数 11 次
5:5  -----------调用了fib函数 14 次
6:8  -----------调用了fib函数 17 次
7:13 -----------调用了fib函数 20 次
8:21 -----------调用了fib函数 23 次
9:34 -----------调用了fib函数 26 次
10:55-----------调用了fib函数 29 次

一目了然,函数被调用的次数大大地减少了!好的想法必须推而广之才能发挥它的最大威力,如果把初始化的值(如 memo 数组)和 计算方式提取出来,就可以完成一个扩展性和复用性更好的组件:

//通用组件
    var memoizer = function(memo, formula) {
        var recur = function(n) {
            var result = memo[n];
            
            if(typeof result !== 'number') {
                result = formula(recur, n);
                memo[n] = result;
            }
            
            return result;
        }
        
        return recur;
    };
    
    var fibonacci = memoizer([0,1], function(recur, n) {
        return recur(n-1) + recur(n-2);
    });
    
    for(var i = 0; i <= 10; i++){
        log('<br>' + i + ':' +fibonacci(i) + '-----------调用了result函数 ' + opt.nMemo + ' 次'); }

通过设计这种产生另一个函数的函数,极大地减少了我们的工作量。例如,要产生一个可记忆的阶乘函数,只需要提供阶乘公式即可:

var factorial = memoizer([1,1], function(recur, n) {
  return n * recur(n-1);
}

参考:javascript语言精髓

原文地址:https://www.cnblogs.com/kuangliu/p/5131021.html