什么是尾递归

Tail Recursion /teɪl rɪˈkɜːrʒn/

In traditional recursion, the typical model is that you perform your recursive calls first, and then you take the return value of the recursive call and calculate the result. In this manner, you don’t get the result of your calculation until you have returned from every recursive call.

In tail recursion, you perform your calculations first, and then you execute the recursive call, passing the results of your current step to the next recursive step. This results in the last statement being in the form of (return (recursive-function params)). Basically, the return value of any given recursive step is the same as the return value of the next recursive call.

示例一 : 累加

Consider a simple function that adds the first N integers. (e.g. sum(5) = 1 + 2 + 3 + 4 + 5 = 15).

Here is a simple JavaScript implementation that uses recursion:

function recsum(x) {if (x === 1) {return x;} else {return x + recsum(x - 1);}
}

If you called recsum(5), this is what the JavaScript interpreter would evaluate:

recsum(5)
5 + recsum(4)
5 + (4 + recsum(3))
5 + (4 + (3 + recsum(2)))
5 + (4 + (3 + (2 + recsum(1))))
5 + (4 + (3 + (2 + 1)))
15

Note how every recursive call has to complete before the JavaScript interpreter begins to actually do the work of calculating the sum.

Here’s a tail-recursive version of the same function:

function tailrecsum(x, running_total = 0) {if (x === 0) {return running_total;} else {return tailrecsum(x - 1, running_total + x);}
}

Here’s the sequence of events that would occur if you called tailrecsum(5), (which would effectively be tailrecsum(5, 0), because of the default second argument).

tailrecsum(5, 0)
tailrecsum(4, 5)
tailrecsum(3, 9)
tailrecsum(2, 12)
tailrecsum(1, 14)
tailrecsum(0, 15)
15

In the tail-recursive case, with each evaluation of the recursive call, the running_total is updated.

示例二 : 斐波那契数列##

在数学上,斐波那契数列以如下被以递推的方法定义:F(1)=1,F(2)=1, F(n)=F(n - 1)+F(n - 2)(n ≥ 3,n ∈ N*)。

换成Java代码如下:

public static long classicFibonacci(long num) {if(num <= 0) {return 0;}else if(num == 1 || num == 2) {return 1;}else {return classicFibonacci(num - 1) + classicFibonacci(num - 2);}
}

用尾递归方法改造一下

public static long tailRecursionFibonacci(long num) {if(num <= 0) {return 0;}else if(num == 1 || num == 2) {return 1;}else {return tailRecursionFibonacci(num, 1, 1, 2);}
}public static long tailRecursionFibonacci(long num, long first, long second, long index) {if(num == index) {return second;}else {return tailRecursionFibonacci(num, second, first + second, index + 1);//尾递归调用}
}

为什么需要尾递归

因为性能。

The consequence of tail recursion is that once you are ready to perform your next recursive step, you don’t need the current stack frame any more. This allows for some optimization. In fact, with an appropriately written compiler, you should never have a stack overflow snicker with a tail recursive call. Simply reuse the current stack frame for the next recursive step.

那么,我们不妨测试一下示例二:斐波那契数列中两种算法。测试方法是用两种算法得出斐波那契数列的第46项是多少且分别消耗多长时间。

首先,用classicFibonacci计算得出斐波那契数列的第46项。

@Test
public void testClassicFibonacci() {System.out.println(classicFibonacci(46));
}

运行结果如下

斐波那契数列的第46项是1836311903,用classicFibonacci得出斐波那契数列的第46项所消耗的时间是43.026秒


接着,用有尾递归方式tailRecursionFibonacci计算得出斐波那契数列的第46项。

@Test
public void testTailRecursionFibonacci() {System.out.println(tailRecursionFibonacci(46));
}

斐波那契数列的第46项是1836311903,跟classicFibonacci的一致。用有尾递归方式的tailRecursionFibonacci得出斐波那契数列的第46项所消耗的时间是0.035秒,是classicFibonacci的1229倍,差距悬殊。

如果继续用classicFibonacci得出斐波那契数列第n项(n>46),将消耗更长时间,甚至天荒地老也没有算完。

参考资料

  1. What is tail recursion?
  2. 斐波那契数列

Tail Recursion尾递归相关推荐

  1. JavaScript, ABAP和Scala里的尾递归(Tail Recursion)

    这是Jerry 2021年的第 12 篇文章,也是汪子熙公众号总共第 283 篇原创文章. 今天是2021年1月20日,看看历史上的今天都发生了什么. 2004年1月20日,第一个公开版本的Scala ...

  2. Recursion and Tail Recursion in Java and Erlang

    2019独角兽企业重金招聘Python工程师标准>>> Typical Recursion Example (hanoi problem) public void move(int ...

  3. 通俗易懂----尾递归

    差距在这: 线性递归:计算到头之后还要再回溯一遍 (相当于运算了两遍) 尾递归 :计算到头就得到结果,不回溯  (就运算一遍) 看代码,简单求阶乘公式: 线性递归: // Line recursion ...

  4. 这才是面试官想听的:详解「递归」正确的打开方式

    来自:码农田小齐 前言 递归,是一个非常重要的概念,也是面试中非常喜欢考的.因为它不但能考察一个程序员的算法功底,还能很好的考察对时间空间复杂度的理解和分析. 本文只讲一题,也是几乎所有算法书讲递归的 ...

  5. 「递归」的正确打开方式,看不懂你打我~

    这是磊哥的第 189 期分享 作者 | 田小齐 来源 | 码农田小齐(ID:NYCSDE) 分享 | Java中文社群(ID:javacn666) 前言 递归,是一个非常重要的概念,也是面试中非常喜欢 ...

  6. 吊打面试官系列:你会「递归」么?

    作者 | 小齐本齐 来源 | 码农田小齐(ID:NYCSDE) 递归,是一个非常重要的概念,也是面试中非常喜欢考的.因为它不但能考察一个程序员的算法功底,还能很好的考察对时间空间复杂度的理解和分析. ...

  7. 30 分钟学 Erlang

    30 分钟学 Erlang (一) Shawn_xiaoyu https://www.jianshu.com/p/b45eb9314d1e 本文写给谁看的? 那些已经有过至少一门编程语言基础,并且需要 ...

  8. Programming Languages PartA Week3学习笔记——SML基本语法第二部分

    文章目录 Building Compound Types Records Tuples as Syntactic Sugar Datatype Bindings Case Expressions Us ...

  9. JAVA程序设计第十版梁勇著答案_0031 Java学习笔记-梁勇著《Java语言程序设计-基础篇 第十版》英语单词...

    第01章 计算机.程序和Java概述 CPU(Central Processing Unit)*中央处理器 Control Unit*控制单元 arithmetic/logic unit /ə'rɪθ ...

最新文章

  1. 未来可能发生的十大颠覆性创新
  2. 继承单例模式 php_详解PHP单例模式之继承碰见的问题
  3. 模板—主席树(待修改)
  4. MAX10 ADC的一些基知识
  5. [XSY] 计数(DP,NTT,分治)
  6. python画图去掉边框
  7. 数据分箱1——人工手动分箱
  8. Solr(二)创建索引和查询索引的基本应用
  9. 网页设计中色彩的应用
  10. NetAssist 网络助手
  11. 对涉密计算机检查内容,保密工作检查内容主要有哪些
  12. cosmo是什么牌子_时尚COSMO - 时尚品牌 - 时尚
  13. 我是怎样把工资从400-4000
  14. 网站关键词怎么合理布局提升网站排名
  15. 关于微信防撤回(文本、图片、语音、视频、名片等...)的Python学习教程
  16. 服务器主体信息截图,puppeteer实现线上服务器任意区域截图
  17. 基于SSM实现的健身房俱乐部管理系统-JAVA【毕业设计、论文、源码、开题报告】
  18. php7.4安装配置,CentOS环境下安装配置PHP 7.4的方法
  19. 学习笔记5--高精地图技术
  20. python 可视化案例_Python之路 08 数据可视化案例

热门文章

  1. WCHAR char CString等常用类型互转
  2. WinCE中命令行工具cvrtbin简介
  3. php查询文件名,php怎么查询文件名
  4. angular4输入有效性_Angular 2 用户输入
  5. python属性_深入理解python对象及属性
  6. Web——Request转发和Response重定向
  7. 【转】Dynamics 365中的事件框架与事件执行管道(Event execution pipeline)
  8. [你必须知道的.NET]第十九回:对象创建始末(下)
  9. linux内核的邻居表,Linux内核报文收发-L3 - Section 3. IP协议、邻居子系统主要是接收、转发和发送三部分...
  10. mq集群要建传输队列吗_面试官:消息队列这些我必问!