【问题标题】:understanding recursion vs loops ruby [closed]了解递归与循环红宝石[关闭]
【发布时间】:2013-09-01 10:11:09
【问题描述】:

我有以下递归方法。我得到一个错误堆栈溢出。它停在-9352。我的问题,堆栈溢出与无限循环相同吗?因为这将继续调用自己。

但是,如果我使用 while、until、do 等进行无限循环,它不会给我同样的堆栈溢出错误。它会一直运行,直到我的系统内存不足。

这是使用 Ruby

def recursion(n)
    print n
    recursion(n-1)
end

recursion(3)

输出:

3
2
1
0
.
.
.
-9352  stack overflow stops

【问题讨论】:

  • 请写下您使用的编程语言。

标签: loops recursion while-loop


【解决方案1】:

递归和循环是可以以不同方式解决类似问题的技术(如 cmets 中所述,它们是图灵等效的,但这不是我的领域)。

每个函数调用都会向调用堆栈添加一个框架。这需要额外的内存,并且随着您的调用链越来越深,它需要更多内存,直到超过某个限制,这会使您的堆栈overflow 和您的程序崩溃。

您的递归代码会向调用堆栈中添加越来越多的帧,并且在内存有限的情况下会导致其溢出。您需要一些方法来告诉递归何时停止,并在内存耗尽之前这样做。这样的条件等价于数学归纳法中的base case,所以通常这样称呼。

在 cmets 中指出的另一个选项是利用 Tail call 优化,它替换堆栈中的当前帧,因此可以防止堆栈溢出。

您的迭代解决方案只需要固定数量的内存。

仅更改计数器或其他预定义变量的,因此不会产生任何内存开销。

如果你不限制输出,理论上它可以无限期地继续下去,但其他一些耗尽或错误很可能会杀死它。但是,这不会是循环本身使用的变量所消耗的内存。

【讨论】:

  • 递归和迭代并没有根本的不同,因为递归相当于一个while循环。此外,堆栈溢出发生点是一些基本常数的暗示介于误导和不正确之间。如果您认为尾调用优化递归违反了您的基本原则,那么您的整个帖子也是完全不正确的。
  • 虽然我在图灵等价的问题上同意你的观点(尽管我承认这方面的知识并不深入,但这不是我的研究领域),我试图保持答案简单并处理具体情况。我不考虑任何编译器优化或边缘情况,因为我只想在他的示例中说明对基本情况的需求。至于堆栈绑定,AFAIK,它是在编译时或程序启动期间确定的。我将编辑我的答案以反映您的评论,随时编辑或发布您自己的答案。
  • 尾调用优化远非边缘情况,用于大多数函数式语言和所有 gcc 编译语言。您可能会想到进程内存,它是在程序启动或编译时间之前定义的。堆栈大小是进程内存的一部分,具体取决于当前分配给堆栈与堆的内存量。堆内存随着程序的执行而变化,因此对于许多语言来说,确定可用的堆栈空间是绝对不可能的。发布一个简单的答案和发布一个非常不正确的答案也是不同的。
  • 谢谢,@SlaterTyranus,不知道这个。很高兴知道。总是乐于学习新东西。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-09-25
  • 1970-01-01
  • 2010-10-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多