【问题标题】:For every recursive algorithm there can be created equivalent non-recursive version?对于每个递归算法,都可以创建等效的非递归版本吗?
【发布时间】:2011-06-14 04:01:39
【问题描述】:

递归很有趣。然而,在安全关键的应用程序中,它被认为是一件危险的事情(我想是因为堆栈溢出?)。

假设您需要处理您最喜欢的语言的子集,它不允许递归 - 这对您来说会是一场灾难吗?

正式问题:对于每个递归函数,都可以创建一个完全等价的非递归函数——这是真的吗?有什么关于它的定理吗?

【问题讨论】:

  • 递归不好玩...它让我的大脑受伤。
  • 递归很棒。它比循环 w.r.t 简单得多。程序验证并生成漂亮的代码。
  • @Caspar Kleijne:真正伤害我大脑的是模拟递归。一些算法只是用递归更好地表达。

标签: recursion


【解决方案1】:

是的,每个这样的函数都可以重写为非递归函数。我不确定是否有正式的方法可以做到这一点,但它在文献中被称为“递归展开”或“递归展开”。

在大多数情况下,要避免递归。尤其是在上述类型的系统中,例如 MISRA-C 禁止递归。

在一些罕见的情况下,递归很方便并使代码更高效,一些二叉树实现等。

但是递归的主要目的是把它教给困惑的学生,这样当他们有经验的时候,他们可以把它教给困惑的学生,这样-... :)

【讨论】:

  • 规范和正式的方法是CPS 转换。这对人类来说基本上是容易出错的。任何理智的算法语言应该得到支持它或通过实现(例如编译器)的一些等效方法。从这个意义上说,在源代码中禁止递归调用的事实确实很愚蠢,因为它促进了鼓励错误的禁用语言设计。
  • 我知道你很久以前写过这篇文章,但这是对递归的非常有限的看法。递归可以安全地完成,尤其是在尾递归是一种特性而不仅仅是潜在优化的语言中。
  • @2-bits 是的。众所周知的语言速度太慢了,无论如何你做什么都无所谓,注定会以字节码膨胀的恶臭而告终,被称为垃圾收集器的摆渡人慢慢地划过,无动于衷地捡垃圾。
  • 更多的语言允许这样做,而不仅仅是Scheme。 Haskell、Kotlin、Scala、F# 甚至 C#(在适当的情况下)都可以以永远不会破坏堆栈的方式处理尾调用,并且一些,比如 Haskell,可以非常快。任何语言应该都可以做到这一点,但对于某些语言来说,这是一种要求,而不是一种优化。 GCC 为 C 执行此操作(进行了优化),我相信您不会称其为慢。递归有用的。 “适用于嵌入式系统”是许多程序员永远不必使用的衡量标准,在我看来,这不是拒绝它的好理由。
【解决方案2】:

这样想:您的程序由一组有限的 CPU 执行(假设您现在有 1 个),执行一系列指令。 CPU如何处理递归?它无法创建自己的新实例来解决稍微简单的问题,因此它使用堆栈来跟踪它的位置。因此,对于任何可以用任何编程语言递归表达的算法,你也可以像 CPU 一样使用堆栈手动“展开”递归。 (请注意,对于许多算法,尤其是只有一次递归调用的算法,通常有更简单的方法来消除递归,例如简单的迭代。)

【讨论】:

  • 另请注意,对于许多其他算法(如树遍历),删除递归的唯一可行方法是手动管理堆栈。另请注意,这甚至不是很有趣,因此在这些情况下您应该坚持使用递归。
【解决方案3】:

通常使用递归,因为没有简单的方法可以知道需要对问题进行多少次迭代。

用简单的循环替换递归函数可能需要大量额外的工作,但如果您认为有必要,通常应该可以。

不过,如果您的递归函数具有适当的基本情况,则堆栈溢出的危险很小。

【讨论】:

    【解决方案4】:

    递归不是免费的

    占用大量内存空间(通常在堆栈上)
    和 CPU 时间(通过使用调用、重建堆栈帧等)。
    在紧密循环中,这使得递归算法比其非递归等效算法慢。

    如果速度和堆栈限制不是问题,请务必保留递归,但如果您在紧密循环中使用递归,(通常是这种情况)
    通过简化递归,您可以获得很多优化选项。

    因为:也许:
    1. 您不需要保存所有这些寄存器/变量
    2. 你不必跳回你原来的地方。
    3. 您不需要为每个递归调用该清理代码
    4.你可以展开一些循环
    5. 您可以在该非递归例程上使用inline 指令。

    我对递归的平均问题是它最常用于的数据结构:

    树需要指针,而指针需要随机内存访问(它们到处乱跳)这不利于缓存的使用,因为顺序访问比随机访问快得多。
    如果将二叉树替换为二维数组,则会浪费 50% 的空间,(除非您将另一棵树倒置在数组中),但您可以获得许多叶子的顺序访问选项而且您不必使用指针,节省空间。

    Example of a binary tree stored in an array
    +-----------------+
    |0eeeeeeeeeeeeeeee| //no pointers needed, parent/child, is y dimension,
    |11       dddddddd| //sibbling is x dimension of the array.
    |2222         cccc|  //The 123 tree is stored root up.
    |33333333       bb|  //Notice how the abc-tree is stored upside down 
    |4444444444444444a|  //The wasted space in the middle, is offset by the fact 
    +-----------------+  //that you do not need up, down, and sibbling pointers.
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-05-16
      • 1970-01-01
      • 2017-06-04
      • 1970-01-01
      • 2011-01-06
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多