斐波那契数列是如下一系列数字:javascript
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, ...
在种子数字 0 和 1 以后,后续的每个数字都是前面两个数字之和。html
斐波那契数列的一个有趣的性质是,数列的当前数字与前一个数字的比值无限趋近于黄金分割数, 1.61803398875…
你可使用斐波那契数列来生成各类各样有趣的东西,好比黄金螺旋 (Golden Spiral),天然界中存在许多黄金螺旋。前端
斐波那契数列(意大利语:Successione di Fibonacci),又译为费波拿契数、费氏数列、黄金分割数列。java
在数学上,斐波那契数列是以递归的方法来定义:git
F(0)=0, F(1)=1, n>1时,F(n)=F(n-1)+F(n-2)。
根据该规则,返回第n个斐波那契数。程序员
function fibonacci(n) { if(n === 0 || n === 1) { return n; } console.log(`fibonacci(${n-1}) + fibonacci(${n-2})`) return fibonacci(n - 2) + fibonacci(n - 1) } fibonacci(5)
思路:不断调用自身方法,直到n为1或0以后,开始一层层返回数据。
问题:使用递归计算大数字时,性能会很是低;另外,递归形成了大量的重复计算(不少函数执行了屡次)。es6
从上面代码的 console 中能够看出,执行了许多相同的运算。若是咱们对中间求得的变量值,进行存储的话,就会大大减小函数被调用的次数。
这是典型的以空间换时间。很明显,函数被调用的次数大大减小,耗时明显缩减。github
let fibonacci = function() { let temp = [0, 1]; return function(n) { let result = temp[n]; if(typeof result != 'number') { result = fibonacci(n - 1) + fibonacci(n - 2); temp[n] = result; // 将每次 fibonacci(n) 的值都缓存下来 } return result; } }(); // 外层当即执行
动态规划:从底部开始解决问题,将全部小问题解决掉,而后合并成一个总体解决方案,从而解决掉整个大问题;
递归:从顶部开始将问题分解,经过解决掉全部分解的小问题来解决整个问题;算法
function fibonacci(n) { let current = 0; let next = 1; let temp; for(let i = 0; i < n; i++) { temp = current; current = next; next += temp; } console.log(`fibonacci(${n}, ${next}, ${current + next})`); return current; }
思路:从下往上计算,首先根据f(0)和f(1)算出f(2),再根据f(1)和f(2)算出f(3),依次类推咱们就能够算出第n项了。
而这种算法的时间复杂度仅为O(n),比递归函数的写法效率要大大加强。编程
function fib(n) { let current = 0; let next = 1; for(let i = 0; i < n; i++) { [current, next] = [next, current + next]; } return current; }
[[解构赋值]](https://developer.mozilla.org...。所以咱们能够用解构赋值,省略temp中间变量。
function fib(n) { let current = 0; let next = 1; while(n --> 0) { // while(n>0) {n--} n--的返回值是n [current, next] = [next, current + next]; } return current; } fib(10)
// 在ES6规范中,有一个尾调用优化,能够实现高效的尾递归方案。 // ES6的尾调用优化只在严格模式下开启,正常模式是无效的。 'use strict' function fib(n, current = 0, next = 1) { if(n == 0) return 0; if(n == 1) return next; // return next console.log(`fibonacci(${n}, ${next}, ${current + next})`); return fib(n - 1, next, current + next); }
=======下面是科普======
一句话,就是指某个函数的最后一步是调用另外一个函数。
// 用代码来讲,就是B函数的返回值被A函数返回了。 function B() { return 1; } function A() { return B(); // return 1 } // 尾调用不必定出如今函数尾部,只要是最后一步操做便可 function f(x) { if (x > 0) { return m(x) } return n(x); } // 下面不属于尾调用:调用函数g以后,还有别的操做 function f(x){ let y = g(x); return y; } function f(x){ return g(x) + 1; } function f(x) { g(x); // 这一步至关于g(x) return undefined }
了解 js 的调用栈咱们知道,当脚本要调用一个函数时,解析器把该函数添加到栈中而且执行这个函数,并造成一个栈帧(调用帧),保存调用位置和内部变量等信息。
若是在函数A的内部调用函数B,那么在A的调用帧上方,还会造成一个B的调用帧。等到B运行结束,将结果返回到A,B的调用帧才会销毁。此时若是函数B内部还调用函数C,那就还有一个C的调用帧,以此类推。例如递归操做,一个调用栈中若是保存了大量的栈帧,调用栈很是长,消耗了巨大的内存,会致使爆栈(栈溢出,stack overflow)。后入先出的结构。
尾调用之因此与其余调用不一样,就在于它的特殊的调用位置。
那么如今,咱们使用尾调用的话,将函数B放到了函数A的最后一步调用,内层函数不引用外层函数的变量,就不用保留外层变量的调用帧了。这样的话内层函数的调用帧,会直接取代外层函数的调用帧。调用栈的长度就会小不少。
固然,若是内层函数B使用了外层函数A的变量,那么就仍然须要保留函数A的栈帧,典型例子便是闭包。
function f() { let m = 1; let n = 2; return g(m + n); } f();
这应当是一次尾调用。由于m+n的值是经过参数传入函数g内部,并无直接引用,所以也不能说保存 f 内部的变量的值。
下面这种状况内层函数会引用外层函数的值,因此当执行到内层函数时外层函数的调用帧还会存在与当前调用栈中。
function f() { let m = 1; let n = 2; return function() { return m+n; }; } f();
总得来讲,若是全部函数的调用都是尾调用,即只保留内层函数的调用帧,作到每次执行时(例如递归操做),一个调用栈中调用帧只有一项,那么调用栈的长度就会小不少,这样须要占用的内存也会大大减小。这就是尾调用优化的含义。
在ES5中,尾调用和其余形式的函数调用同样:脚本引擎建立一个新的函数栈帧而且压在当前调用的函数的栈帧上面。也就是说,在整个函数栈上,每个函数栈帧都会被保存,这有可能形成调用栈占用内存过大甚至溢出。
在ES6中,strict模式下,知足如下条件,尾调用优化会开启,此时引擎不会建立一个新的栈帧,而是清除当前栈帧的数据并复用:
ES6的尾调用优化只在严格模式下开启,正常模式是无效的。
这是由于在正常模式下,函数内部有两个变量,能够跟踪函数的调用栈。
arguments:返回调用时函数的参数。
func.caller:返回调用当前函数的那个函数。
尾调用优化发生时,函数的调用栈会改写,所以上面两个变量就会失真。严格模式禁用这两个变量,因此尾调用模式仅在严格模式下生效。
函数调用自身,称为递归。若是尾调用自身,就称为尾递归。
递归很是耗费内存,由于须要同时保存成千上百个调用帧,很容易发生"栈溢出"错误(stack overflow)。但对于尾递归来讲,因为只存在一个调用帧,因此永远不会发生"栈溢出"错误。
因而可知,"尾调用优化"对递归操做意义重大。
尾递归的实现,每每须要改写递归函数,确保最后一步只调用自身。作到这一点的方法,就是把全部用到的内部变量改写成函数的参数。
例如实现 fibonacci 函数须要用到两个中间变量 current 和 next,那就把这个中间变量改写成函数的参数。
// 实现阶乘 复杂度 O(n) function factorial(n) { if (n === 1) return 1; return n * factorial(n - 1); } // 尾递归 只保留一个调用帧,复杂度 O(1) function factorial(n, total = 1) { if (n === 1) return total; return factorial(n - 1, n * total); }
一个前端眼中的斐波那契数列
JAVASCRIPT解斐波那契(FIBONACCI)数列的实用解法
JavaScript 调用栈、尾递归和手动优化
尾调用优化
【译】我从用 JavaScript 写斐波那契生成器中学到的使人惊讶的 7 件事