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语言精髓