zoukankan      html  css  js  c++  java
  • 尾调用优化和尾递归改写

    1 尾调用

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

    # 是尾调用
    def f(x):
        return g(x)
    
    # 不是尾调用,因为调用函数后还要执行加法,加法才是最后一步操作
    def f(x):
        return 1+g(x)
    

    2 尾调用优化

    函数调用有一个调用栈,栈内保存了这个函数内部的变量信息。函数掉用就是切换不同的调用帧,从而保证每个函数有独立的运行环境。因为尾调用是函数的最后一步操作,所以在进入被尾调用函数之前并不需要保留外层函数的运行时环境,因为调用位置、内部变量等信息都不会再用到了,只要直接用内层函数的调用记录,取代外层函数的调用记录就可以了。这就叫做"尾调用优化"(Tail call optimization),即只保留内层函数的调用记录。如果所有函数都是尾调用,那么完全可以做到每次执行时,调用记录只有一项,这将大大节省内存。这就是"尾调用优化"的意义。

    尾递归

    如果尾调用自身,就称为尾递归。递归非常耗费内存,因为需要同时保存成千上百个调用记录,很容易发生"栈溢出"错误(stack overflow)。但对于尾递归来说,由于只存在一个调用记录,所以永远不会发生"栈溢出"错误。

    def factorial(n) {
      if (n == 1) return 1;
      return n * factorial(n - 1);
    }
    
    factorial(5) // 120
    

    上面代码是一个阶乘函数,计算n的阶乘,最多需要保存n个调用记录,复杂度 O(n) 。如果改写成尾递归,只保留一个调用记录,复杂度 O(1) 。

    def factorial(n, total) {
      if (n == 1) return total;
      return factorial(n - 1, n * total);
    }
    
    factorial(5, 1) // 120
    

    4 递归函数的改写

    尾递归的实现,往往需要改写递归函数,确保最后一步只调用自身。做到这一点的方法,就是把所有用到的内部变量改写成函数的参数。比如上面的例子,阶乘函数 factorial 需要用到一个中间变量 total ,那就把这个中间变量改写成函数的参数。这样做的缺点就是不太直观,第一眼很难看出来,为什么计算5的阶乘,需要传入两个参数5和1?

    两个方法可以解决这个问题。方法一是在尾递归函数之外,再提供一个正常形式的函数。

    def tailFactorial(n, total) {
      if (n === 1) return total;
      return tailFactorial(n - 1, n * total);
    }
    
    def factorial(n) {
      return tailFactorial(n, 1);
    }
    
    factorial(5) // 120
    

    上面代码通过一个正常形式的阶乘函数 factorial ,调用尾递归函数 tailFactorial ,看起来就正常多了。

  • 相关阅读:
    Maven基本操作命令
    Android studio安装与配置
    java数据结构----图
    java数据结构----堆
    java数据结构----哈希表
    java数据结构----树
    java数据结构----链表
    java数据结构----队列,优先级队列
    java数据结构----栈
    java数据结构----数组篇
  • 原文地址:https://www.cnblogs.com/ZeroTensor/p/10503607.html
Copyright © 2011-2022 走看看