当前位置: 首页 > 知识库问答 >
问题:

方案和调用堆栈中的递归

金伟
2023-03-14

我是一名大学生,学习球拍/方案和C作为我的CS学位的入门课程。

我在网上读到,在C语言中使用迭代而不是递归通常是最佳实践,因为递归由于将堆栈帧保存到调用堆栈上而代价高昂。。。

现在,在类似于Scheme的函数式语言中,递归一直在使用。我知道尾部递归在Scheme中是一个巨大的优势,据我所知,它只需要一个堆栈帧(有人能澄清这一点吗?)无论递归有多深。

我的问题是:非尾递归呢?每个函数应用程序是否保存在调用堆栈上?如果我能得到一个如何工作的简要概述,或给我一个资源,我将不胜感激;我似乎找不到一个明确说明这一点的地方。

共有2个答案

胡霖
2023-03-14

是的,处于非尾部位置的调用需要向堆栈中添加一些内容,以便它知道如何在调用返回时恢复工作。(有关堆栈、尾部调用和非尾部调用的更透彻解释,请参阅Steele的论文,该论文揭穿了“昂贵的过程调用”的神话,或被认为有害的过程调用实现,或Lambda:the Ultimate GOTO,链接自readscheme.org的Lambda papers页面。)

但是Racket(以及许多其他方案和一些其他语言)实现了“堆栈”,这样即使有深度递归,也不会耗尽堆栈空间。换句话说,球拍没有堆栈溢出。其中一个原因是,支持深度递归的技术与支持一类连续性的技术是一致的,Scheme标准也需要这种技术。您可以在Clinger等人的《第一类连续性的实现策略》中阅读到这些内容。

贡可人
2023-03-14

方案需要消除尾调用。不是尾调用递归的代码将需要额外的堆栈帧。

暂时让我们假设javascript支持尾部调用优化,第二个函数定义将只使用一个堆栈帧,而第一个函数定义由于,将需要额外的堆栈帧。

function sum(n) {
   if (n === 0)
      return n;
   return n + sum(n - 1);
}

function sum(n) {
  function doSum(total, n) {
    if (n === 0)
       return total;
    return doSum(total + n, n - 1);
  }
  return doSum(0, n);
}

通过将计算结果放在堆栈上,可以编写许多递归函数来进行尾部调用优化

第一个定义的概念调用如下所示

3 + sum(2)
3 + sum(2) = 3 + 2 + sum(1)
3 + sum(2) = 3 + 2 + sum(1) = 3 + 2 + 1 + sum(0)
3 + sum(2) = 3 + 2 + sum(1) = 3 + 2 + 1 + sum(0) = 3 + 2 + 1 + 0
3 + sum(2) = 3 + 2 + sum(1) = 3 + 2 + 1 + sum(0) = 6
3 + sum(2) = 3 + 2 + sum(1) = 6
3 + sum(2) = 6
6

第二个定义的调用如下所示

sum(3, sum(2)) = sum(5, sum(1)) = sum(6, sum(0)) = 6
 类似资料:
  • 让我们回到函数,进行更深入的研究。 我们的第一个主题是 递归(recursion)。 如果你不是刚接触编程,那么你可能已经很熟悉它了,那么你可以跳过这一章。 递归是一种编程模式,在一个任务可以自然地拆分成多个相同类型但更简单的任务的情况下非常有用。或者,在一个任务可以简化为一个简单的行为加上该任务的一个更简单的变体的时候可以使用。或者,就像我们很快会看到的那样,处理某些数据结构。 当一个函数解决一

  • 问题内容: 在Java多线程中,术语和之间在语义上有区别吗? 问题答案: 每个线程都有自己的调用堆栈,“调用堆栈”和“线程堆栈”是同一件事。将其称为“线程堆栈”只是强调了调用堆栈特定于线程。 Bill Venners将此称为Java堆栈: 启动新线程时,Java虚拟机将为该线程创建一个新的Java堆栈。如前所述,Java堆栈将线程的状态存储在离散的帧中。Java虚拟机仅直接在Java堆栈上执行两项

  • 我是Android Studio(2.1.2)的新手,我试图在调试会话期间找到调用堆栈。在堆栈溢出问题上,我发现了一个我认为最完美的问题:Android Studio-调试Android应用程序时,我在哪里可以看到callstack。。。但是所有的答案都只引用了一个显示当前正在运行的线程的窗口。如果我在断点处停止,它会显示一个红色复选标记,指示我在该线程中停止。但单击它并不会扩展到调用堆栈。 “调

  • 代码运行良好。只是我不明白。在递归部分有困难的。在此部分中:我的想法是,首先它将一直执行直到一个阈值。则它将执行一次。因此只会被赋值一次。显然那不是真的。 对我来说,困难的部分是在方法中,做什么?在方法中,、做什么?

  • 嘿,我在JUnit(4.12)中的SystemOutRule日志和测试有问题。 我有一个简单的测试类: null

  • 问题内容: 内核堆栈和用户堆栈有什么区别?为什么要使用内核堆栈?如果在ISR中声明了局部变量,它将存储在哪里?每个进程都有自己的内核堆栈吗?那么,进程如何在这两个堆栈之间进行协调? 问题答案: 内核堆栈和用户堆栈有什么区别? 简而言之,除了在内存中使用不同的位置(并因此为堆栈指针寄存器使用不同的值)之外,什么也没有,而且通常使用不同的内存访问保护。也就是说,在用户模式下执行时,即使映射了内核内存(