尾递归优化

最近学习了尾递归优化,发现与我之前认识的尾递归完全不一样,特此记录

什么是尾递归

  1. 在我刚开始知道尾递归的时候,小看了尾递归,从字面意思去理解,这不就是在函数的末尾调用自身不就是尾递归了吗。这有什么意义吗?还特殊起一个名词。
  2. 后面我发现我还是太年轻了,不但错误理解了尾递归,还不知道尾递归是用来优化内存的。
  3. 尾递归的真正定义:
    1. 在函数的最后一步!!!
      最后一步!!!
      最后一步!!!调用自身
    2. 并且在回归过程中不能做任何操作!!!
      那就是在回归过程中不能做任何操作!!!
      那就是在回归过程中不能做任何操作!!!
    3. 符合上述两个条件才可称该递归为尾递归。
    4. 注意函的数最后一步和在函数末尾调用自身是不一样的。尾递归的递归调用不是必须出现在函数的末尾。

首先需要分辨哪些递归才是尾递归

  1. 虚假的尾递归,以下递归看似是尾递归,实则不是尾递归。因为以下递归发返回的结果还要进行n*操作,所以不是尾递归的。
function x(n){
	return n === 1 ? 1 : n * x(n-1);
}
  1. 真正的尾递归
function x(n, sum){
	return n === 1 ? sum : x(n-1, n * sum);
}

递归的优缺点

  1. 递归的缺点

    • 递归相比循环有更大的内存消耗,从数据结构的角度上来讲,就是一个普通循环的空间复杂度是1,而递归的空间复杂度是n。为什么递归的时间复杂度是n?因为递归n次,就要将函数入栈n次,每一次入栈都会有函数参数的声明。所以是n。
    • 递归的执行效率实际上也是更慢的。尽管循环的时间复杂度是n,递归的时间复杂度也是n,但是无论是函数入栈、入栈,分配、释放局部变量的内存、保存函数断点、保存被调用函数的返回结果,这些操作实际上都需要时间。
  2. 递归的优点

    • 有更好的可读性和可维护性
    • 复杂问题用递归解决比循环更为优雅
    • 某些问题只能通过递归去解决。所有的循环的都可以用递归实现,但是不是所有的递归都可以转换成循环去完成。(这里我也看过递归循环可以互相转化的观点,大家可以讨论一下)
  3. 可以看到递归缺点很多,但优点依然明显,某些时候不得不用。

  4. 用递归经常遇到的问题就是栈溢出,因为每次递归都要将函数入栈分配空间,递归次数特别多的时候是会爆栈的,尽管它有合理的退出条件。

为什么尾递归可以优化内存

  1. 我们看尾递归的定义,在函数最后一步递归且在回归过程中不能做任何操作。因为这个函数在回归过程中不用进行任何操作,所以在函数调用栈里保存外层函数的执行信息是没有必要的。当外层函数尾递归调用内层函数的时候,直接用内层函数顶替外层函数在函数调用栈里的位置就可以了。区别只是参数值变化了。不需要一层一层将递归函数入栈到递归调用栈里就优化了栈内存。
  2. 有人会说我只是语言使用者,在语言层面无法操作函数调用栈啊?不用担心,机智的语言设计者发现了这一点。基本所有的现代编程语言,在你使用尾递归时,都不会将递归函数重复循环的入栈,而是直接顶替外层函数。所以只要你的递归可以优化成尾递归调用方式并将其改成尾递归,那么你就完成了优化。
  3. 所以递归函数的空间复杂度是n,如果你用了尾递归那么空间复杂度就变成了1。执行效率上也得到了提高。
原文地址:https://www.cnblogs.com/cndeveloper/p/14396795.html