• 从菲波那切数列看尾部调用优化


    1、菲波那切数列

      在数学上,斐波那契数列以如下被以递归的方法定义:F0=0,F1=1,Fn=Fn-1+Fn-2(n>=2,n∈N*),用文字来说,就是斐波那契数列列由 0 和 1 开始,之后的斐波那契数列系数就由之前的两数相加。形如:

    0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233……
    

      数学上的计算公式是:

       

      读书时候还有利用线性方程推算过这个公式,不过现在都忘了差不多了 ~~泪奔~~。

      其实用代码描述兔子生娃的故事也没少干,常见算法有递归法和递推法。

    2、递归

    function fibonacci(n){
        if(n === 1 || n === 0 ) return n;
        return fibonacci(n-1) + fibonacci(n-2);
    } 
    

    说明: 

      递归造成了大量的重复计算,使用递归计算大数字时,性能会特别低。

      函数调用会在内存形成一个"调用记录",又称"调用帧"(call frame),保存调用位置和内部变量等信息。如果在函数A的内部调用函数B,那么在A的调用记录上方,还会形成一个B的调用记录。等到B运行结束,将结果返回到A,B的调用记录才会消失。如果函数B内部还调用函数C,那就还有一个C的调用记录栈,以此类推。所有的调用记录,就形成一个"调用栈"(call stack)。因而,当递归层数过大之后,就可能造成调用栈占用内存过大或者溢出。

    3、递推法

    function fibonacci(n) {
        let current = 0;
        let next = 1;
        for(let i = 0; i < n; i++){
            [current, next] = [next, current + next];
        }
        return current;
    }

    4、 尾调用优化

     尾调用优化是指某个函数的最后一步是调用另一个函数。

    最简单模式:

    function f(x){
      return g(x);
    }

      结合上面的递归说明,尾调用由于是函数的最后一步操作,所以不需要保留外层函数的调用记录,因为调用位置、内部变量等信息都不会再用到了,只要直接用内层函数的调用记录,取代外层函数的调用记录就可以了。   

    菲波那切数列改写成尾调用写法:

    'use strict'
    function fibonacci(n, current = 0, next = 1) {
    	if(n === 1) return next;
    	if(n === 0) return 0;
    	return fibonacci(n - 1, next, current + next);
    }
    

    5、动态规划 

    function fibonacci(n) {
      var n1 = 1, n2 = 1, sum;
      for (let i = 2; i < n; i++) {
        sum = n1 + n2
        n1 = n2
        n2 = sum
      }
      return sum
    }
    

     参考:阮一峰--《尾调用优化》

     

  • 相关阅读:
    进程池和线程池
    TCP并发、GIL、锁
    进程间通信
    装饰器与反射
    装饰器大全
    面向对象三大特征: 封装 继承 多态
    面向对象 魔术方法
    魔术方法
    ubuntu 中导 tarfile,win 不亲切
    os VS shutil
  • 原文地址:https://www.cnblogs.com/leaf930814/p/8644370.html
Copyright © 2020-2023  润新知