【问题标题】:Is there any limit to recursion in lisp?lisp中的递归有什么限制吗?
【发布时间】:2011-02-28 23:16:26
【问题描述】:

我喜欢尽可能使用递归,这似乎是一种比实际循环更自然的循环方式。我想知道lisp中的递归是否有任何限制?就像在 python 中一样,它在 1000 次循环后会发疯? 你能用它来比方说游戏循环吗?

现在测试一下,简单的计数递归函数。现在 > 7000000!

非常感谢

【问题讨论】:

  • 享受递归就像享受锤子。有时它是最好的工具,但当您想拧螺丝时,请使用螺丝刀。
  • 我喜欢递归的想法。它让我着迷。但是,我永远不会在任何商业广告中真正使用它。当然,它使代码更容易编写,但以牺牲效率和内存消耗为代价。为了安全起见,我更喜欢迭代版本。

标签: recursion lisp common-lisp


【解决方案1】:

方案要求优化尾调用,一些 CL 实现也提供它。但是,CL 并没有强制要求。

请注意,要使尾调用优化起作用,您需要确保不必返回。例如。 Fibonacci 的幼稚实现,需要返回并添加到另一个递归调用,不会进行尾调用优化,因此您将耗尽堆栈空间。

【讨论】:

  • +1 特别提到并非所有递归都是尾调用。
  • 你能给我写一个例子来说明什么可以避免,这会否定尾调用吗?
  • @Isaiah:阶乘的简单实现:(defun fact (n) (if (> n 0) (* n (fact (- n 1))) 1)) 原因是乘法是在递归调用返回之后完成的。
  • 我会将“一些 CL 实现”替换为“大多数 CL 实现”。 @Greg Hewgill:为了清楚起见,* 调用将被尾调用(但这无助于递归)。
【解决方案2】:

首先,您应该了解尾调用的含义。

尾调用是不消耗堆栈的调用。 现在您需要识别何时消耗堆栈。

我们以阶乘为例:

(defun factorial (n)
    (if (= n 1)
        1
        (* n (factorial (- n 1)))))

这是阶乘的非尾递归实现。 为什么?这是因为除了从阶乘返回之外,还有一个未决的计算。

(* n ..)

因此,每次调用阶乘时,您都在堆叠 n。 现在让我们编写尾递归阶乘:

(defun factorial-opt (n &key (result 1))
    (if (= n 1)
        result
        (factorial-opt (- n 1) :result (* result n))))

在这里,结果作为参数传递给函数。 所以你也在消耗堆栈,但不同的是堆栈大小保持不变。 因此,编译器可以通过只使用寄存器并将堆栈留空来优化它。

factorial-opt 这样更快,但可读性较差。 factorial 仅限于堆栈大小,factorial-opt 则不会。 所以你应该学会识别尾递归函数才能知道递归是否有限。

可能有一些编译器技术可以将非尾递归函数转换为尾递归函数。也许有人可以在这里指出一些链接。

【讨论】:

  • 在 Common Lisp 中,尾部调用(即尾部位置的调用)不一定不消耗任何堆栈空间。这取决于优化设置和编译器。
  • 毫无疑问,我的阶乘计算从 2700 到 3650,但在那之后它使 Stack 溢出。这是一个很好的信息。[另一个有用的答案](stackoverflow.com/questions/15269193/…)
猜你喜欢
  • 2014-11-26
  • 2011-12-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-05-22
  • 2020-11-16
  • 2020-07-31
相关资源
最近更新 更多