首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

面试官:说一说递归如何优化-尾递归优化

递归非常耗费内存,因为需要同时保存成千上百个调用记录,很容易发生"栈溢出"错误。但对于尾递归来说,由于只存在一个调用记录,所以永远不会发生"栈溢出"错误。...如果按照阮一峰老师讲解完,大家还是没有太理解的话,我把我个人的理解说一下: 假如使用了尾递归优化,在执行到最后一行的时候,其实就可以看成,就是这一个函数mutiply(n-1, n * total)在执行...这样做的缺点就是不太直观,第一眼很难看出来,为什么计算5的阶乘,需要传入两个参数5和1? 两个方法可以解决这个问题。 方法一:是在尾递归函数之外,再提供一个正常形式的函数。...总结一下,递归本质上是一种循环操作。纯粹的函数式编程语言没有循环操作命令,所有的循环都用递归实现,这就是为什么尾递归对这些语言极其重要。...五、尾递归优化的魅力 从下图中,我们就可以看出,单单是求5的阶乘,就提升了5ms之快,可以说厉害的惊人了! ? 六、使用条件 - 严格模式 ES6的尾调用优化只在严格模式下开启,正常模式是无效的。

4.1K22

大家都知道递归,尾递归呢?什么又是尾递归优化?

尾递归又是啥? 我得知这个概念,最开始还是因为很多年前一次面试,面试官问我“你知道什么是尾递归吗?”,我以为是“伪”递归,难道是假的递归???当初我也是懵逼状态(当初面试官忍住没笑也是厉害了 )。...尾递归优化 当你给编译选项开了优化之后,见证奇迹的时刻到了,居然能算出正确结果。如图所示: ? C++ 默认 segmentation fault, 开启编译优化后,能正常计算结果。...原因就是因为编译器帮助做了尾递归优化,可以打开汇编代码看看(这里就不展示 C++的了)。后面我用大家比较熟悉的 JVM based 语言 Scala 来阐述这个优化过程。...默认启用尾递归优化正常计算结果,禁用尾递归优化则“StackOverflow”。 我们来看看生成的字节码有什么不同。 ? 包含尾递归优化的字节码,直接 goto 循环。 ?...当然对于像 scala 这样,有一些语法糖能够帮助校验和验证,也是一个不错的选择。但递归转迭代的能力,我们能具备岂不更好。

1.5K30
  • 您找到你想要的搜索结果了吗?
    是的
    没有找到

    如何编写高质量的 JS 函数(3) --函数式编程

    3、函数内部保存数据 闭包的存在使得函数内保存数据得到了实现。函数执行,数据存在不同的闭包中,不会产生相互影响,就像面对对象中不同的实例拥有各自的自私有数据。多个实例之间不存在可共享的类成员。...我推荐一篇文章,阐述的非常透彻。 对于这三个高级知识点,我有些个人的看法。 第一个:不要被名词吓到,通过敲代码去感受其差异性。...就目前而言,浏览器对尾递归优化的支持还不是很好。 什么是尾递归?...第二张图,使用了尾递归,最后一个表达式就是递归函数本身。 问题来了,为什么说 JS 对尾递归支持的不好呢?...PS: 任何需求都是有优先级的,对浏览器来说,像这种尾递归优化的优先级,明显不高。我个人认为,优先级不高,是到现在极少有浏览器支持尾递归优化的原因。

    1.7K00

    来来来,我们聊一聊,为什么不建议使用递归操作?

    但我们在听到这句话的时候,是否会产生过疑问,为什么不建议使用递归操作呢? 现在,我们就一起聊聊这个话题,看看递归到底会产生什么样的问题。 首先,我们思考一道算法题:如何实现二叉树的中序遍历?...对于树的遍历,无论是前序、中序还是后序遍历,大家可能下意识的就会想到递归,为什么呢?因为递归操作实现起来“简单”啊,而且树的结构完美契合了递归的应用场景!...仍以实现二叉树的中序遍历为例,在上述的递归实现之上,我们新增了一个int类型的参数level,作为递归可执行的最大次数,代码示例为: public List inorder(TreeNode...因此,像我们上面实现的二叉树的中序遍历,就很难用尾递归的形式来改写,因为递归形式的中序遍历需要在遍历左右子树之间,把结果存起来,从而给在函数最后一行调用函数自身的形式造成了很大的困难。...尾递归结果:832040 尾递归形式:递归 30 次,耗时 38125 纳秒。 如上述结果所示,尾递归与普通递归相比,快了近 128 倍。虽然这样的测试还很粗糙,但也足以说明两者的性能差异啦!

    45920

    来来来,我们聊一聊,为什么不建议使用递归操作?

    递归的问题 如题,我们可能或多或少的都听见过类似的话或者建议: 尽量少使用递归操作,甚至干脆就不要使用递归操作。 但我们在听到这句话的时候,是否会产生过疑问,为什么不建议使用递归操作呢?...对于树的遍历,无论是前序、中序还是后序遍历,大家可能下意识的就会想到递归,为什么呢?因为递归操作实现起来“简单”啊,而且树的结构完美契合了递归的应用场景!...仍以实现二叉树的中序遍历为例,在上述的递归实现之上,我们新增了一个int类型的参数level,作为递归可执行的最大次数,代码示例为: public List inorder(TreeNode...因此,像我们上面实现的二叉树的中序遍历,就很难用尾递归的形式来改写,因为递归形式的中序遍历需要在遍历左右子树之间,把结果存起来,从而给在函数最后一行调用函数自身的形式造成了很大的困难。...尾递归结果:832040 尾递归形式:递归 30 次,耗时 38125 纳秒。 如上述结果所示,尾递归与普通递归相比,快了近 128 倍。虽然这样的测试还很粗糙,但也足以说明两者的性能差异啦!

    96100

    尾调用优化

    尾调用(Tail Call)是函数式编程的一个重要概念,本文介绍它的含义和用法。 一、什么是尾调用? 尾调用的概念非常简单,一句话就能说清楚,就是指某个函数的最后一步是调用另一个函数。...递归非常耗费内存,因为需要同时保存成千上百个调用记录,很容易发生"栈溢出"错误(stack overflow)。但对于尾递归来说,由于只存在一个调用记录,所以永远不会发生"栈溢出"错误。...这样做的缺点就是不太直观,第一眼很难看出来,为什么计算5的阶乘,需要传入两个参数5和1? 两个方法可以解决这个问题。方法一是在尾递归函数之外,再提供一个正常形式的函数。...factorial ,调用尾递归函数 tailFactorial ,看起来就正常多了。...总结一下,递归本质上是一种循环操作。纯粹的函数式编程语言没有循环操作命令,所有的循环都用递归实现,这就是为什么尾递归对这些语言极其重要。

    79950

    【数据结构与算法】【小白也能学的数据结构与算法】递归 分治 迭代 动态规划 无从下手?一文通!!!

    尾递归和非尾递归 尾递归是指递归函数在递归调用的最后一步执行,且递归调用的返回值直接作为当前递归函数的返回值。尾递归的优点是可以通过尾递归优化,将递归转化为迭代,减少函数调用的内存消耗。...所以我们要根据题目的具体情况而选定用哪种(其实实际上两种都能互相解决各自的问题 我一般直接用尾递归就好了) 递归的边界条件和终止条件 递归的边界条件和终止条件非常重要,它们决定了递归何时停止并返回结果。...分治和递归的定义和特点 分治和递归都是常见的问题解决方法,它们在一定程度上有相似之处,但也存在一些区别。...递归的特点包括: 问题可以通过相同的问题的较小实例的解来表示 递归函数调用自身来解决较小实例 递归调用必须有终止条件,否则会导致无限递归 分治和递归之间的联系和区别 分治和递归之间存在一些联系和区别。...分治算法通常需要明确的分解和合并步骤,而递归算法则更关注问题的分解和终止条件。 代码示例解析 下面我们通过一个代码示例来说明分治和递归的使用。

    15410

    在Java中谈尾递归--尾递归和垃圾回收的比较(转载)

    我不是故意在JAVA中谈尾递归的,因为在JAVA中谈尾递归真的是要绕好几个弯,只是我确实只有JAVA学得比较好,虽然确实C是在学校学过还考了90+,真学得没自学的JAVA好 不过也是因为要绕几个弯,所以才会有有意思的东西可写...,不仅不用释放上一个,连下一个新的都不用开,效率非常高(有人做实验,这个比递推比迭代都要效率高) 为什么写成尾递归的形式,编译器就能优化了?...因此,在栈中,只保存有基本类型的变量和对象引用。而引用所指向的对象保存在堆中。...与栈不同,堆的空间不会随着方法调用结束而清空(即使它在栈上的引用已经被清空了)(也不知道为什么不直接同步清空)。因此,在某个方法中创建的对象,可以在方法调用结束之后,继续存在于堆中。...那为什么呢,我看到有的说法是:JAVA编写组不实现尾递归优化是觉得麻烦又没有太大的必要,就懒得实现了(原话是:在日程表上,但是非常靠后),官方的建议是不使用递归,而是使用while循环,迭代,递推 转载

    1.4K50

    尾调用

    但对于递归来说,由于只存在一个调用帧,所以永远不会发生”栈溢出“错误。...这样的缺点是不太直观,第一眼很难看出来,为什么计算 5 的阶乘需要传入两个参数 5 和 1? 有两个方法可以解决这个问题。方法一是在尾递归函数之外再提供一个正常形式的函数。...总结以下,递归本质是一种循环操作。纯粹的函数式编程没有循环操作命令,所有循环都用递归实现,这就是为什么尾递归对于这些语言极其重要。...对于其他支持”尾调用优化“的语言(比如 Lua、ES6),只需要知道循环可以用递归代替,而一旦使用递归,就最好使用尾递归。 严格模式 ES6 的尾调用优化只在严格模式下开启,正常模式下是无效的。...尾递归优化只在严格模式下生效,那么在正常模式下, 或者在那些不支持该功能的环境中,有没有办法啊使用尾递归优化呢?

    17520

    ️ Stack Overflow: 调试与解决递归调用问题

    在我的博客中,我主要分享技术教程、Bug解决方案、开发工具指南、前沿科技资讯、产品评测、使用体验、优点推广和横向对比评测等内容。今天,我们将深入探讨递归调用问题的调试与解决策略。...解决方案: 增加递归深度限制:调整系统或编程语言的栈深度限制。 优化递归算法:使用尾递归优化或将递归转换为迭代。...代码示例(尾递归优化): // 尾递归优化示例 public int factorial(int n, int accumulator) { if (n == 0) return accumulator...(n - 1); } } 3.3 单元测试 编写单元测试验证递归函数的正确性和性能,确保其在各种输入情况下正常工作。...关注我的博客,获取更多技术干货和最新资讯!

    12910

    Java初学者的30个常见问题

    为什么数组下标从0 开始 而不是从 1 开始? A. 这种传统起源于机器语言的编程方法。在机器语言中,数组下标被用来计算元素位置与第一个元素之间的偏移量。...你需要牢记传值参数(参数是基本变量类型)和传引用参数(比如数组)之间的区别。 Q. 那为什么不把所有的参数都使用传值的方式,包括对待数组? A. 但数组很大时,复制数组需要大量的性能开销。...不肯能,所有的递归调用都可以用循环来表示。比如你可以用while的方式来实现栈。 Q. 那我应该选择哪个,递归的方式 还是 循环的方式? A. 根据代码的可读性和效率性之间做权衡。 Q....我担心使用递归代码时的空间开销和重复计算(例如用递归解Fibonacci)的问题。有没有其他需要担心的? A....尾递归是极其重要的,不用尾递归,函数的堆栈耗用难以估量,需要保存很多中间函数的堆栈。

    1.8K51

    Python 递归函数

    由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出) 先举个简单的例子:计算1到100之间相加之和;通过循环和递归两种方式实现 # 循环方式 def sum_cycle(n):...RecursionError: maximum recursion depth exceeded in comparison **解决递归调用栈溢出的方法是通过尾递归优化,事实上尾递归和循环的效果是一样的....html) 尾递归基于函数的尾调用, 每一级调用直接返回函数的返回值更新调用栈,而不用创建新的调用栈, 类似迭代的实现, 时间和空间上均优化了一般递归!...存在的问题 虽然尾递归优化很好, 但python 不支持尾递归,递归深度超过1000时会报错 一个牛人想出的解决办法 实现一个 tail_call_optimized 装饰器 #!...、后调用栈的变化和tail_call_optimized装饰器抛异常退出递归调用栈的作用, 我这里利用 pudb调试工具 做了动图 开启尾递归优化前的调用栈 开启尾递归优化后(tail_call_optimized

    1.3K30

    尾递归的后续探究

    那么为什么V8引擎都已经实现了尾调用优化,但是默认不开启呢? 3 尾调用优化默认关闭 V8 blog里有这么一篇文章《ES6, ES7 and beyond》给了我们对应的解释。...为了写出正确的尾递归方法,你需要首先了解是不是正确的尾调用形式。同时你可能还需要尝试写不同的尾递归和普通递归的写法,调整递归参数让能超过调用栈,并不断的进行调试。...而且你也不能保证调试出来的结果是不是因为运气好而表现出了正常的结果。这也就是隐式优化所带来的一大问题。...4 STC 尾调用优化存在的问题其实是在于其优化过程是不受开发者控制和了解的,所以来自 Mozilla 和微软的委员提出从语法上指定尾部调行为(Syntactic Tail Call)。...下使用尾递归写法的方法依旧出现调用栈溢出的原因在于: 直接原因: 各大浏览器(除了safari)根本就没部署尾调用优化 根本原因: 尾调用优化依旧有隐式优化和调用栈丢失的问题 参考资料 朋友你听说过尾递归吗

    1K100

    finished with exit code -1073740791 (0xC0000409)

    一旦达到操作系统分配给进程堆栈的最大空间限制,就会导致堆栈溢出,进而引发这个错误。解决方案1. 优化递归函数如果程序中存在递归函数并且递归深度过大,可以优化递归函数以减少堆栈空间的使用。...可以采用尾递归、迭代或者其他算法来替代递归。2. 增加堆栈空间可以通过修改编译器、链接器选项或者程序运行参数来增加堆栈空间的大小。具体的方法因编程语言和开发工具而异。...为了解决这个问题,可以优化递归函数、增加堆栈空间、修复代码逻辑错误,或借助工具定位问题。通过这些方法,可以有效地应对这种错误并保证程序的正常运行。以下是一个示例代码,演示了递归函数优化的实际应用场景。...: {fib}")# 优化后的尾递归方式计算斐波那契数列的第 10000 个数fib_tail = fibonacci_tail(10000)print(f"优化后的尾递归方式计算斐波那契数列的第 10000...但是,当计算第 10000 个数时,普通递归方式会导致堆栈溢出错误,而优化后的尾递归方式可以正常计算出结果。 这个示例代码展示了如何通过优化递归函数来避免堆栈溢出错误,并提升程序的性能和可靠性。

    99940

    Go 函数式编程篇(五):递归函数及性能调优

    : 一个问题的解可以被拆分成多个子问题的解 拆分前的原问题与拆分后的子问题除了数据规模不同,求解思路完全一样 子问题存在递归终止条件 需要注意的是,编写递归函数时,这个递归一定要有终止条件,否则就会无限调用下去...二、通过斐波那契数列求解演示 下面我们就以递归函数的经典示例 —— 斐波那契数列为例,演示如何通过 Go 语言基于上述归纳的思路编写递归函数来打印斐波那契数列。...具体细节我就不一一解释了,如果你理解了 Go 装饰器模式的实现,很容易理解这段代码。...尾递归优化是函数式编程的重要特性之一,在了解尾递归优化前,我们先来看看什么是尾递归。...尾递归的实现需要重构之前的递归函数,确保最后一步只调用自身,要做到这一点,就要把所有用到的内部变量/中间状态变成函数参数,以 fibonacci 函数为例,就是 fibonacci(n-1) 和 fibonacci

    46520

    尾递归的后续探究

    那么为什么V8引擎都已经实现了尾调用优化,但是默认不开启呢? 3 尾调用优化默认关闭 V8 blog里有这么一篇文章《ES6, ES7 and beyond》给了我们对应的解释。...为了写出正确的尾递归方法,你需要首先了解是不是正确的尾调用形式。同时你可能还需要尝试写不同的尾递归和普通递归的写法,调整递归参数让能超过调用栈,并不断的进行调试。...而且你也不能保证调试出来的结果是不是因为运气好而表现出了正常的结果。这也就是隐式优化所带来的一大问题。...4 STC 尾调用优化存在的问题其实是在于其优化过程是不受开发者控制和了解的,所以来自 Mozilla 和微软的委员提出从语法上指定尾部调行为(Syntactic Tail Call)。...下使用尾递归写法的方法依旧出现调用栈溢出的原因在于: 直接原因: 各大浏览器(除了safari)根本就没部署尾调用优化 根本原因: 尾调用优化依旧有隐式优化和调用栈丢失的问题 参考资料 朋友你听说过尾递归吗

    1.5K22

    深入理解java.util.concurrent.ExecutionException: java.lang.StackOverflowError异常

    // 代码示例import java.util.concurrent....在这种实现中,当计算阶乘的数字较大时,就有可能发生栈溢出的情况。栈溢出是一种典型的递归调用导致的错误。每当方法调用自身时,虚拟机都会将当前方法的状态信息(局部变量、方法参数等)保存在栈帧中。...使用尾递归优化尾递归是一种特殊的递归形式,在尾递归中,递归调用是方法的最后一个操作。通过使用尾递归优化,编译器可以将递归调用转换为循环,从而避免栈溢出的问题。...然而,Java并没有对尾递归进行显式的优化支持。如果你想在Java中使用尾递归,你需要手动将递归调用转换为迭代形式,或者使用第三方库,如LambdaJ或Trampoline库,来实现尾递归优化。...为了解决这个问题,我们可以优化递归算法,避免递归深度过大;增加栈的容量;或者使用尾递归优化。根据具体的场景和需求,选择合适的方法来解决栈溢出异常问题。处理并发编程中的异常是开发人员需要面对的挑战之一。

    59610

    Python 中的递归,你真的懂了吗?

    还说超过了最大递归深度限制,为什么要限制呢?   通俗来讲: 是因为每个函数在调用自己的时候还没有退出,占内存,多了肯定会导致内存崩溃。 ...用递归求斐波那契数列、汉诺塔 对初学者来讲可能理解起来不太容易,所以我们用阶乘和二分查找来给大家演示一下。  求阶乘:   任何大于1的自然数n阶乘表示方法:     n!...尾递归:   如果一个函数中所有递归形式的调用都出现在函数的末尾,我们称这个递归函数是尾递归的。当递归调用是整个函数体中最后执行的语句且它的返回值不属于表达式的一部分时,这个递归调用就是尾递归。...尾递归代码示例:  def calc(n):     print(n - 1)     if n > -50:         return calc(n-1) 我们之前求的阶乘是尾递归么?...所以不是尾递归。因为每个活跃期的返回值都依赖于用n乘以下一个活跃期的返回值,因此每次调用产生的栈帧将不得不保存在栈上直到下一个子调用的返回值确定。

    69020

    C 语言函数:入门指南

    C 语言中的函数声明和定义 您可以通过以下方式创建并调用函数: // 创建一个函数 void myFunction() { printf("我刚被执行了!")...; } 另一个例子: 如果我们使用上一章关于函数参数和返回值的示例: int myFunction(int x, int y) { return x + y; } int main() { int...递归示例 将两个数字相加很容易,但将一系列数字相加就比较复杂了。...在以下示例中,递归用于通过将问题分解为将两个数字相加的简单任务来将一系列数字相加: int sum(int k); int main() { int result = sum(10); printf...四舍五入 ceil() 函数将数字向上舍入到最接近的整数,floor() 函数将数字向下舍入到最接近的整数,并返回结果: printf("%f", ceil(1.4)); printf("%f", floor

    27010

    【翻译】Rust中的尾递归优化的故事

    注意: 我不会在这篇文章里解释尾调用的概念。下面是一些比较好的相关资料: Youtube频道 Computerphile[1] 有一个视频[2],详细讲解了尾递归函数的示例。...StackOverflow[3]上有个关于尾递归概念的详细解释。 随着最近几年编程社区强调函数范式和函数式风格的趋势,您可能会认为尾调用优化已经出现在许多编译器/解释器的实现中。...在深入探究为什么会这样之前,让我们简要地总结一下尾调用优化背后的思想。...有了上面这些知识,让我们回来看看,为什么Rust没有做TCO。 回顾Rust的时光机 我能找到的最早关于Rust中尾调用优化的相关资料,可以追溯到Rust项目的开始阶段。...我发现了来自2013年的这些邮件列表[6],在这些邮件列表中,Graydon Hoare详细列出了关于为什么他认为尾调用优化不属于Rust的观点。 ?

    2K20
    领券