尾递归与尾递归优化

关于递归:

  • 递归是什么:递归,简单的说就是函数调用其自身
  • 如何递归:(1)在函数里调用自身。 (2)设定递归出口,也就是递归终止条件。
  • 递归是如何执行的:函数执行时会把数据存入栈中,执行完后出栈,调用子函数的话,就把子函数数据入栈,等子函数执行完出栈,回到父函数执行,执行完再出栈,以此类推。因此,递归深度其实就是使用栈的深度。
  • 递归的缺点:就是栈的问题,很可能因为递归过深,造成栈溢出

关于尾递归:

  • 百科解释:如果一个函数中所有递归形式的调用都出现在函数的末尾,我们称这个递归函数是尾递归的。
  • 这个解释感觉有点看不懂,因为平时都是把return写末尾啊。但明显那不是尾递归。
  • 百科解释:当递归调用是整个函数体中最后执行的语句且它的返回值不属于表达式的一部分时,这个递归调用就是尾递归。
  • 有点似懂非懂,是不是尾递归就是return时只有函数本身呢,比如 return f(x);。返回值不属于表达式一部分,排除 return f(x)+1;这种函数。

个人理解:尾递归重点在于递归时,函数除了返回递归函数的值,别的都是是执行完毕的。即除了返回子函数的返回值,没有多余的操作

换个方式讲述:其实递归是可以理解为一颗树,即递归树。我们以斐波那契数列为例。

1 int Fib(int n){
2  
3    if(n<=1) return n;//终止递归的条件
4  
5    else return Fib(n-1)+Fib(n-2);//递归步骤
6  
7 }

 

这个代码的递归树是这样的,这是典型的树形结构。箭头标记了执行的方向。

可以发现有两个特点:(1)这是树形结构,有分叉。(2)每次箭头执行到叶子节点后,总要返回回来(回溯)。

         

但其实最重要的是回溯,因为分叉造成回溯,尾递归的关键就是回溯。如果我们去掉分叉,那么就变成了左上方的图。如果我们去掉回溯,就变成右上方的图。

可以认为右上方的图就是一个尾递归。去掉返回的箭头并不是代表它不回溯,而是说明回溯是没有必要的,因为它回溯时除了返回值没做什么多余的事情。(尾递归优化可以认为是去掉了这个回溯,正常的执行是有回溯的

 

尾递归最重要的特点:回溯时除了返回调用的函数的返回值没做别的事。

 

 如何设计尾递归:

  1. 尾递归的递归树不能有分叉,即递归树必然是条链,有分叉就意味着回溯时有必要的操作。
  2. 让回溯时没有多余的操作。(最重要的就是这个)

关于尾递归的优化:

对于优化。我们先回到栈,为什么回溯要用到栈。栈是用来存储函数执行所需的数据。如果我们递归调用后,不需要当前函数的数据呢?是不是就没必要存储这些信息了(就好像去掉了那个回溯的箭头)。

所以有些编译器对尾递归进行了优化,即递归调用时,不让函数数据新入栈,而是直接替换栈中当前函数的数据。这就实现了尾递归的优化。

转载请注明:旅途@KryptosX » 尾递归与尾递归优化

时间: 2025-01-21 07:57:09

尾递归与尾递归优化的相关文章

浅谈尾递归的优化方式

在上文<尾递归与Continuation>里,我们谈到了尾递归的概念和示例,不过有些朋友对于尾递归的 功效依然有所怀疑.因此现在,老赵再简单讲解一下尾递归的优化原理,希望能给大家以一定理性认识. 尾递归的循环优化 尾递归,即是递归调用放在方法末尾的递归方式,如经典的阶乘: int FactorialTailRecursion(int n, int acc) { if (n == 0) return acc; return FactorialTailRecursion(n - 1, acc *

Python与尾递归优化介绍

什么是尾递归 有很多时候,使用递归的方式写代码要比迭代更直观一些,以下面的阶乘为例: def factorial(n):      if n == 0:         return 1     return factorial(n - 1) * n 但是这个函数调用,如果展开,会变成如下的形式: factorial(4)  factorial(3) * 4  factorial(2) * 3 * 4  factorial(1) * 2 * 3 * 4  factorial(0) * 1 * 2

Lua中的函数(function)、可变参数、局部函数、尾递归优化等实例讲解_Lua

一.函数 在Lua中,函数是作为"第一类值"(First-Class Value),这表示函数可以存储在变量中,可以通过参数传递给其他函数,或者作为函数的返回值(类比C/C++中的函数指针),这种特性使Lua具有极大的灵活性.   Lua对函数式编程提供了良好的支持,可以支持嵌套函数.   另外,Lua既可以调用Lua编写的函数,还可以调用C语言编写的函数(Lua所有的标准库都是C语言写的).   定义一个函数 复制代码 代码如下: function hello() print('he

Scala:尾递归的跟踪调用及其局限

在7.2节中,我们提到过想要把更新var的while循环转换成仅使用val的更函数式风格的话,有时候你 可以使用递归.下面的例子是通过不断改善猜测数字来逼近一个值的递归函数: def approximate(guess: Double): Double = if (isGoodEnough(guess)) guess else approximate(improve(guess)) 这样的函数,带合适的isGoodEnough和improve的实现,经常用在查找问题中.如果想要approxima

python中尾递归用法实例详解

  这篇文章主要介绍了python中尾递归用法,较为详细的分析了尾递归原理与相关使用技巧,非常具有实用价值,需要的朋友可以参考下 本文实例讲述了python中尾递归用法.分享给大家供大家参考.具体分析如下: 如果一个函数中所有递归形式的调用都出现在函数的末尾,我们称这个递归函数是尾递归的.当递归调用是整个函数体中最后执行的语句且它的返回值不属于表达式的一部分时,这个递归调用就是尾递归.尾递归函数的特点是在回归过程中不用做任何操作,这个特性很重要,因为大多数现代的编译器会利用这种特点自动生成优化的

诊断 Java 代码: 提高 Java 代码的性能 尾递归转换能加快应用程序的速度,但不是所有的 JVM 都会做这种转换

简介: 很多算法用尾递归方法表示会显得格外简明.编译器会自动把这种方法转换成循环,以提高程序的性能.但在 Java 语言规范中,并没有要求一定要作这种转换,因此,并不是所有的 Java 虚拟机(JVM)都会做这种转换.这就意味着在 Java 语言中采用尾递归方法将导致巨大的内存占用,而这并不是我们期望的结果.Eric Allen 在本文中阐述了动态编译将会保持语言的语义,而静态编译则通常不会.他说明了为什么这是一个重要问题,并提供了一段代码来帮助判断您的即时(JIT)编译器是否会在保持语言语义的

递归与尾递归(C语言)

在计算机科学领域中,递归式通过递归函数来实现的.程序调用自身的编程技巧称为递归( recursion). 一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解, 递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量.递归的能力在于用有限的语句来定义对象的无限集合. 一般来说,递归需要有:边界条件.递归前进段和递归返回段. 当边界条件不满足时,递归前进:当边界条件满足时,递归返回.

关于尾递归的使用详解_php实例

这几天看到几篇关于尾递归的文章,之前对尾递归没有多大概念,所以回头研究了一下尾递归.  尾递归的概念尾递归(Tail Recursion)的概念是递归概念的一个子集.对于普通的递归,由于必须要记住递归的调用堆栈,由此产生的耗用是难以估量的.比如下文中php小节第一个例子使用php写一个阶乘函数,就是由于递归造成了栈溢出的错误.尾递归出现的目的就是消除递归栈耗损这个缺憾的. 从代码层面看,尾递归其实一句话就可以说清楚了: 函数的最后一个操作是递归调用  比如"菲波纳锲"数列的php的递归

尾递归详细总结分析_C 语言

一. 尾递归与Continuation 递归与尾递归关于递归操作,相信大家都已经不陌生.简单地说,一个函数直接或间接地调用自身,是为直接或间接递归.例如,我们可以使用递归来计算一个单向链表的长度: 复制代码 代码如下: public class Node{    public Node(int value, Node next)    {        this.Value = value;        this.Next = next;    }     public int Value {