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

漫谈递归转非递归

其中,具体要保存的内容包括:局部变量、形参、调用函数地址、返回值。那么,如果递归调用N次,就要分配N*局部变量、N*形参、N*调用函数地址、N*返回值。这势必是影响效率的。...递归就是利用系统的堆栈保存函数当中的局部变量来解决问题的,说白了就是利用堆栈上的一堆指针指向内存中的对象,并且这些对象一直不被释放,直到遇到简单情境时才一一出栈释放,所以总的开销就很大。...这就相当于执行完函数B后,函数A也执行完了,从数据结构上看,在执行函数B时,函数A的堆栈已经大部分被函数B修改或替换了,所以,栈空间没有递增或者说递增的程度没有普通递归那么大。...可见,尾递归其实是将普通递归转换成一种迭代的形式,下一层递归所用的栈帧可以与上一层有重叠,局部变量可重复利用,不需要额外消耗栈空间,也没有push和pop。 这样就大大减少了递归调用栈的开销。...这种方法几乎是通用的方法,因为递归本身就是通过堆栈实现的,我们只要把递归函数调用的局部变量和相应的状态放入到一个栈结构中,在函数调用和返回时做好push和pop操作,就可以了(后面有一个模拟快排的例子)

1.8K70

合并两个排序的链表

前言 给定两个递增排序的链表,如何将这两个链表合并?合并后的链表依然按照递增排序。本文就跟大家分享一种解决方案,欢迎各位感兴趣的开发者阅读本文。...同样的,这个问题也可以用双指针的思路来实现: p1指针指向链表1的头节点 p2指针指向链表2的头节点 声明一个变量存储合并后的链表,比对两个指针指向的节点值大小: 如果p1指针指向的节点值比p2指向的值小...没错,这就是典型的递归思路,代码如下: 声明一个函数MergeLinkedList,它接受2个参数:递增排序的链表1,递增排序的链表2 递归的基线条件:链表1为null就返回链表2,链表2为null就返回链表...1 声明一个变量pMergedHead用于存储合并后的链表头节点 如果当前链表1的节点值小于链表2的节点值 pMergedHead的值就为链表2的节点值 pMergedHead的下一个节点值就为链表1的下一个节点和链表...2的节点值比对后的值(递归) 否则 pMergedHead的值就为链表1的节点值 pMergedHead的下一个节点值就为链表2的下一个节点和链表1的节点值比对后的值(递归) 最后,返回pMergedHead

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

    翻译连载 | 第 9 章:递归(下)-《JavaScript轻量级函数式编程》 |《你不知道的JS》姊妹篇

    堆栈帧中包含了函数语句当前状态的某些重要信息,包括任意变量的值。之所以这样,是因为一个函数暂停去执行另外一个函数,而另外一个函数运行结束后,引擎需要返回到之前暂停时候的状态继续执行。...注意: 如果这些函数间没有相互调用,而只是依次执行 -- 比如前一个函数运行结束后才开始调用下一个函数 baz(); bar(); foo(); -- 则堆栈帧并没有产生;因为在下一个函数开始之前,上一个函数运行结束并把它的帧从堆栈里面移除了...保持堆栈帧跟踪函数调用的状态,并将其分派给下一个递归调用迭。...这样的话,当其余参数 ...nums 再次进行递归调用时候,为了得到其与 num1 累加的结果,必须要保留上一次递归调用的堆栈帧。...在弹簧床格式的代码中,同样的创建了类似 CPS 的后续函数,不同的是,它们没有被传递,而是被简单的返回了。 不再是函数调用另外的函数,堆栈的深度也不会大于一层,因为每个函数只会返回下一个将调用的函数。

    1.1K50

    数据结构与算法:递归算法

    阶乘的基本情况是 n = 0。当 n = 0 时,我们返回 1。 为什么递归会出现Stack Overflow错误? 如果未达到或未定义基本情况,则可能会出现堆栈溢出问题。...如果堆栈上的内存被这些函数耗尽,就会导致堆栈溢出错误。 直接递归和间接递归有什么区别? 如果函数 fun 调用相同的函数 fun,则该函数被称为直接递归。...indirectRecFun1(); // Some code... } 递归中如何为不同的函数调用分配内存? 当从 main() 调用任何函数时,都会在堆栈上为其分配内存。...递归函数调用自身,被调用函数的内存分配在分配给调用函数的内存之上,并且为每个函数调用创建不同的局部变量副本。当达到基本情况时,函数将其值返回给调用它的函数,并且内存被解除分配,并且该过程继续。...在语句 2 中,调用printFun(2),为 **printFun(2)**分配内存,并将局部变量 test 初始化为 2,并将语句 1 到 4 压入堆栈。

    19310

    递归函数实现 HelloWorld 的详细推理及实际示例

    递归步骤:打印当前下标字符后,调用自身,并将 index + 1 传入。通过这样一个函数,HelloWorld 字符串会逐个字符被递归打印,最终实现完整输出。...只有当回音逐渐消失到你听不到的时候,整个过程才会停止,这就如同递归到达基准条件时函数停止调用一样。执行过程中的堆栈机制在计算机中,递归是通过函数调用堆栈来实现的。...每当一个函数被调用时,都会在内存中创建一个新的堆栈帧来保存函数的参数和局部变量。当递归调用发生时,每个递归步骤都会在栈中创建新的帧,直到基准条件被满足,函数开始从栈中一层一层地弹出,逐步返回。...例如,二叉树的遍历用递归来实现非常简洁。在 HelloWorld 的例子中,递归并不是最有效的解决方案,但它展示了如何将一个简单的问题通过递归的方式来解决,帮助我们理解递归的工作原理。...硬件中的调用栈(call stack)在管理函数调用和返回上扮演了至关重要的角色。每个递归调用都会在栈上分配内存,并保存函数的局部变量和返回地址。

    9000

    递归

    写递归代码的关键: 找到如何将大问题分解为小问题的规律,并基于此写出递推公式,然后再推敲出终止条件,最后将其翻译为代码 注:对于递推,不要试图用人脑去分解递归的每个步骤,关键是抽象出递归公式,和找到终止条件...2.递归代码要警惕堆栈溢出 我们在栈那一节有讲过,函数调用会使用栈来保存临时变量。...每调用一个函数,都会将临时变量封装为帧栈压入内存栈,等函数执行完成时,才出栈。 而系统栈或者虚拟机栈空间一般都不大。 如果递归求解的数据规模很大,调用层次很深,一直压入栈,就会有堆栈溢出的风险。...那么,要怎么避免出现堆栈溢出呢? 我们可以通过在代码中限制递归调用的最大深度的方式来解决。 就是递归调用超过一定深度之后,我们就不继续往下递归了,直接返回报错。...在时间效率上,递归代码里多了很多的函数调用, 当这些函数调用的数量较大时,就会积聚成一个可观的时间成本。

    82440

    递归的递归之书:引言到第四章

    为c()调用创建一个新的帧对象并将其放置在调用堆栈上,其中包含c()的局部spam变量 ❻。随着这些函数的返回,帧对象从调用堆栈中弹出。程序执行知道要返回到哪里,因为返回信息存储在帧对象中。...这个帧是存储所有局部变量和参数(如number)的地方。因此,对于调用堆栈上的每个帧都有一个单独的number变量。...但当基本情况返回并且帧从调用堆栈中弹出时,其下面的帧有自己的局部变量number,其值始终为1。当执行返回到调用堆栈中的前一个帧时,递归调用后的代码会被执行❹。这就是导致数字升序出现的原因。...该程序通过使用列表作为堆栈数据结构(存储在callStack变量中❶)来模拟调用堆栈,从而模拟递归函数调用。存储返回地址信息和nthNumber本地变量的字典模拟了帧对象❷。...卡片底部是函数调用返回的head + sum(tail)表达式。当创建一个新的递归函数时,一个新的卡片被推到堆栈上。当函数调用返回时,顶部的卡片从堆栈中弹出。

    64210

    递归最佳解析

    我们回想下之前说过的栈数据结构,不清楚的朋友可以翻阅历史文章。函数调用会使用栈来保存临时变量,每次调用一个函数都会把临时变量封装成栈帧压入线程对应的栈中,等方法结束返回时,才出栈。...如果递归的数据规模比较大,调用层次很深就会导致一直压入栈,而栈的大小通常不会很大就会导致堆栈溢出的情况。...我们只能在代码里面限制最大深度,直接返回错误,使用一个全局变量表示递归的深度,每次执行都 + 1,当超过指定阈值还没有结束的时候直接返回错误。...hasSovledMap.get(n); } int ret = f(n-1) + f(n-2); hasSovledMap.put(n, ret); return ret; } 递归的空间复杂度因为每次调用都会在栈上保存一次临时变量...如何将递归转换成非递归代码 递归有利有弊,递归写起来很简洁,而不好的地方就是空间复杂度是 O(n),有堆栈溢出风险,存在重复计算。要根具体情况来选择是否需要递归。

    56940

    数据结构与算法 --- 递归(一)

    递归的堆栈溢出问题 在函数调用会使用栈来保存临时变量,每调用一个新的函数,都会将临时变量封装为栈帧,压入内存栈,等函数执行完成后,再将栈帧出栈,所以,如果递归求解的数据规模很大,调用层次很深,一直往函数栈里添加数据...如何避免出现堆栈溢出呢?「可以通过在代码中限制递归调用的最大深度」。...为了避免重复,可以使用字典将计算过的值存储下来,当递归调用到已经计算过的值时,直接从字典中取值并返回,这样就省掉了重复计算。...是,理论上所有递归算法都可以改写为迭代循环的非递归写法。这是因为递归算法本质上是一个函数在自己内部不断调用自己,而迭代循环可以通过变量的更新来达到相同的效果。...具体来说,可以通过使用一个栈或队列等数据结构来模拟递归函数的调用过程。每当递归函数需要调用自身时,将当前的参数值和程序计数器等信息保存到栈或队列中,然后继续执行下一个语句。

    27920

    数据结构与算法 --- 递归(一)

    递归的堆栈溢出问题 在函数调用会使用栈来保存临时变量,每调用一个新的函数,都会将临时变量封装为栈帧,压入内存栈,等函数执行完成后,再将栈帧出栈,所以,如果递归求解的数据规模很大,调用层次很深,一直往函数栈里添加数据...如何避免出现堆栈溢出呢?「可以通过在代码中限制递归调用的最大深度」。...为了避免重复,可以使用字典将计算过的值存储下来,当递归调用到已经计算过的值时,直接从字典中取值并返回,这样就省掉了重复计算。...是,理论上所有递归算法都可以改写为迭代循环的非递归写法。这是因为递归算法本质上是一个函数在自己内部不断调用自己,而迭代循环可以通过变量的更新来达到相同的效果。...具体来说,可以通过使用一个栈或队列等数据结构来模拟递归函数的调用过程。每当递归函数需要调用自身时,将当前的参数值和程序计数器等信息保存到栈或队列中,然后继续执行下一个语句。

    36820

    数据结构与算法 --- 递归(二)

    探究产生堆栈溢出的原因 函数调用采用「函数调用栈」来保存当前“快照”(局部变量,返回地址等)。函数调用栈是内存中开辟的一块存储空间,它被组织成“栈”这种数据结构,数据先进后出。...递归的过程包含大量的函数调用,如果递归求解的数据规模很大,函数调用层次很深,那么函数调用栈中的数据(栈帧)会越来越多,而函数调用栈空间一般不大,堆栈空间不足以存储所有的调用信息,从而导致堆栈溢出。...(n - 1) 的结果(即 result )相乘后将结果返回。...讨论尾递归避免堆栈溢出 什么是尾递归? 「尾递归是指一个递归函数的最后一个操作是递归调用自身,并且该调用的返回值直接返回给函数的调用者,而不进行任何其他的计算或处理。这种形式的递归称为尾递归」。...上文说到,函数调用栈中保存局部变量和返回地址,而对于尾递归代码,递归代码出现在最后一行中,返回之后不需要继续往下执行,因此,返回地址可以不用保存;而局部变量 n 也被移动到新的函数 Factorial(

    18310

    01- JavaScript 调用堆栈

    让我们打破之前的定义: LIFO:当我们说调用堆栈是按照后进先出的数据结构原理进行操作时,这意味着当函数返回时,被压入堆栈的最后一个函数是第一个弹出的函数。...临时存储 调用一个函数时,该函数,其参数和变量将被推入调用堆栈以形成堆栈框架,该堆栈是堆栈中的内存位置。当函数返回时(从栈弹出),将清除内存。 ? ?...管理功能调用 调用堆栈回鹘每一个堆栈帧位置的记录。它知道下一个要执行的功能,并在执行后将其删除,这就是使得 JavaScript 中的代码执行顺序同步的原因。 调用堆栈如何处理函数调用?...是什么导致堆栈溢出? 当存在没有出口点的递归函数(调用自身的函数)时,将发生堆栈溢出。...综上所诉 调用堆栈的主要收获是: 它是单线程的,每次只能做一件事情。 代码执行是同步的 函数调用会创建一个占用临时内存的堆栈 它的作用是 LIFO,先进后出

    1.4K20

    JavaScript 中的尾调用和优化

    为什么说尾调用重要呢,原因是它不会在调用栈上增加新的堆栈帧,而是直接更新调用栈,调用栈所占空间始终是常量,节省了内存,避免了爆栈的可能性。...用上面的栗子来说,尾调用的调用栈是这样的: [f(x)] => [g(x)] 由于进入下一个函数调用时,前一个函数内部的局部变量(如果有的话)都不需要了,那么调用栈的长度不会增加,可以直接跳入被尾调用的函数...如果是非尾调用的情况下,调用栈会长这样: [f(x)] => [1 + g(x)] 可以看到,调用栈的长度增加了一位,原因是 f 函数中的常量 1 必需保持保持在调用栈中,等待 g 函数调用返回后才能被计算回收...尾递归 顾名思义,在一个尾调用中,如果函数最后的尾调用位置上是这个函数本身,则被称为尾递归。递归很常用,但如果没写好的话也会非常消耗内存,导致爆栈。...而尾递归之所以可以优化,是因为每次递归调用的时候,当前作用域中的局部变量都没有用了,不需要层层增加调用栈再在最后层层回收,当前的调用帧可以直接丢弃了,这才是尾调用可以优化的原因。

    1.1K10

    数据结构-递归

    所以 n 个台阶的走法就等于先走 1 阶后,n-1 个台阶的走法 加上先走 2 阶后,n-2 个台阶的走法。...递归代码要警惕堆栈溢出 我在“栈”那一节讲过,函数调用会使用栈来保存临时变量。每调用一个函数,都会将临时变量封装为栈帧压入内存栈,等函数执行完成返回时,才出栈。系统栈或者虚拟机栈空间一般都不大。...如果递归求解的数据规模很大,调用层次很深,一直压入栈,就会有堆栈溢出的风险。 那么,如何避免出现堆栈溢出呢? 我们可以通过在代码中限制递归调用的最大深度的方式来解决这个问题。...递归调用超过一定深度(比如 1000)之后,我们就不继续往下再递归了,直接返回报错。还是电影院那个例子,我们可以改造成下面这样子,就可以避免堆栈溢出了。...递归代码还有很多别的问题。 在时间效率上,递归代码里多了很多函数调用,当这些函数调用的数量较大时,就会积聚成一个可观的时间成本。

    52120

    数据结构与算法-递归

    递归代码的注意事项 a.递归代码要警惕堆栈溢出 由于在函数调用时会使用栈来保存临时变量,每调用一个函数,都会将临时变量封装为栈帧压入内存栈,等函数执行完成返回时,才出栈。...如果递归求解的数据规模很大,调用层次很深,一直压入栈,就会有堆栈溢出的风险。 那么该如何避免堆栈溢出呢? 我们可以通过在代码中限制递归调用的最大深度的方式来解决这个问题。...递归调用超过一定深度(比如 1000)之后,我们就不继续往下再递归了,直接返回报错。对于排队买票的例子,我们可以改造成下面这样子,就可以避免堆栈溢出了。...递归代码还有很多别的问题。 在时间效率上,递归代码里多了很多函数调用,当这些函数调用的数量较大时,就会积聚成一个可观的时间成本。...怎么将递归代码改写为非递归代码? 递归有利有弊,利是递归代码的表达力很强,写起来非常简洁;而弊就是空间复杂度高、有堆栈溢出的风险、存在重复计算、过多的函数调用会耗时较多等问题。

    68110

    递归和迭代小结

    递归分为两个阶段: 1)递推:把复杂的问题的求解推到比原问题简单一些的问题的求解; 2)回归:当获得最简单的情况后,逐步返回,依次得到发杂的解。...优点: 1)大问题化为小问题,可以极大的减少代码量; 2)用有限的语句来定义对象的无限集合.; 3)代码更简洁清晰,可读性更好 缺点: 1)递归调用函数,浪费空间; 2)递归太深容易造成堆栈的溢出; 迭代...迭代:利用变量的原值推算出变量的一个新值.如果递归是自己调用自己的话,迭代就是A不停的调用B。...所谓迭代关系,指如何从变量的前一个值推出其下一个值的公式(或关系)。迭代关系式的建立是解决问题的关键,通常可以使用递推或倒推的方法来完成。 (3)对迭代过程进行控制。在什么时候结束迭代过程?...2) 能用迭代的不用递归,递归调用函数,浪费空间,并且递归太深容易造成堆栈的溢出.

    14410

    Python 之父的解析器系列之五:左递归 PEG 语法

    试试看吧……我们可以尝试记录在调用堆栈上的 expr() 的(左递归)调用次数,并将其与下面表达式中“+” 运算符的数量进行比较。如果调用堆栈的深度大于运算符的数量,则应该返回 false。..._getframe() 来实现它,但有更好的方法:让我们反转调用的堆栈! 这里的想法是我们从 oracle 返回 false 处调用,并保存结果。这就有了expr()->term()->'foo' 。...很容易编写一个 oracle 来实现,它应该在首次调用时就返回 false——不需要检查堆栈或向前回看。...这新的结果会更新 memo 缓存(那个 node 实例),然后开始下一个迭代。 再次调用未装饰的 expr(),这次截获的递归调用返回新缓存的 Node 实例(一个 term)。...当走到 while 循环时,它失望地发现这个结果比最后一个短,就中断了,将更长的结果((foo + bar)+ baz )返回给原始调用,就是初始化了外部 expr() 调用的地方(例如,一个 statement

    83630

    用于规划的分层有限状态控制器| IJCAI2016杰出论文详解

    分层FSCs能够包含多重独立的FSCs。 2. 每个FSC能够调用其他的FSCs。 3. 每个FSC都由个参数表,当一个FSC被调用时,必须调整指派给参数的变量。...作为一个特例,我们的改进通过允许一个FSC用不同的变量调用它自身来实现递归。 为了进一步解释,图2展示了分层FSC C[n]使用递归方法实现了二元树的DFS反复运动。...,FSC的C [n的周期]具有前往树的最右边的分支的所有节点,直至到达节点的效果。此外,通过分配n的子(使用动作copyL(n,子)),使递归调用调用时,FSC C [n]被递归所有左子执行。...然而,当 Ti(q,s) = (q’,Cj[p])将一个指令返还给控制器Cj[p] ∈ Z时,我们将下一个等级的堆栈设置为 (q0,s[p]),其中s[p]是从s中获得的——通过复制每个p中的变量对象到...在下一个等级的堆栈中语句的执行伴随着转换函数Tj,,其中包括其它需要更高堆栈等级的FSC指令。如果 Tj 到达终端状态(q⊥,s⊥),控制就会返回到根控制器Ci。

    76540

    邂逅栈

    栈的相关用法 需求介绍 栈的介绍 利用数组实现栈 栈实现综合计算器 前缀表达式(波兰表达式) 中缀表达式 后缀表达式 递归 递归使用的场景 递归原则 递归实现迷宫问题 8皇后问题 在邂逅了完线性结构的数组和队列后...栈的应用场景 子程序的调用:在跳往子程序前,会先将下个指令的地址存到堆栈中,直到子程序执行完后再将地址取出,以回到原来的程序中。...处理递归调用:和子程序的调用类似,只是除了储存下一个指令的地址外,也将参数、区域变量等数据存入堆栈中。 表达式的转换[中缀表达式转后缀表达式] 与求值 (实际解决)。 二叉树的遍历。...递归 递归就是方法自己调用自己, 且每次调用时传入不同变量 递归的出现是帮助解决复杂问题, 简化代码 递归代码举例 public static int factorial(int n) { if (...递归必须向退出递归的条件逼近,否则就是无限递归,出现StackOverflowError,死龟了:) 当一个方法执行完毕,或者遇到return,就会返回,遵守谁调用,就将结果返回给谁,同时当方法执行完毕或者返回时

    45510
    领券