【问题标题】:Is there a problem that has only a recursive solution? [duplicate]是否存在只有递归解决方案的问题? [复制]
【发布时间】:2020-12-12 01:00:24
【问题描述】:

可能的重复:
Is there a problem that has only a recursive solution?
Can every recursion be converted into iteration?
“Necessary” Uses of Recursion in Imperative Languages

是否存在只有递归解决方案的问题,即有递归解决方案但尚未找到迭代解决方案的问题,或者更好的是,已证明不存在(显然,这不是尾递归)?

【问题讨论】:

  • 这取决于你所说的递归。每个递归函数都可以通过实现堆栈转换为非递归函数..
  • 是的;这里有详细描述:stackoverflow.com/questions/1094679
  • @Marc Gravell:太棒了!
  • @Marc Gravell:太棒了,我们可以在查看 K&R 中的递归索引时发现同样的情况。 (索引引用了一堆页面和索引页面...)
  • 我认为这更多的是“与递归相比,迭代是否存在非常糟糕的问题”而不是“是否存在绝对需要递归的问题”

标签: algorithm recursion


【解决方案1】:

将函数调用替换为将参数推入堆栈,并通过弹出堆栈返回,这样就消除了递归。

编辑:回应“使用堆栈不会降低空间成本”

如果递归算法可以在恒定空间中运行,那么它可以写成尾递归的方式。如果它以尾递归格式编写,那么任何体面的编译器都可以折叠堆栈。但是,这意味着“将函数调用转换为显式堆栈推送”方法也需要常量空间。以阶乘为例。

阶乘:

def fact_rec(n):
    ' Textbook Factorial function '
    if n < 2:  return 1
    else:      return n * f(n-1)


def f(n, product=1):
    ' Tail-recursive factorial function '
    if n < 2: return product
    else:     return f(n-1, product*n)

def f(n):
    ' explicit stack -- otherwise same as tail-recursive function '
    stack, product = [n], 1
    while len(stack):
        n = stack.pop()
        if n < 2: pass 
        else:
            stack.append(n-1)
            product *= n
    return product

因为 stack.pop() 在循环中跟在 stack.append() 之后,所以堆栈中的元素永远不会超过一项,因此它满足了常量空间的要求。如果您想象使用临时变量而不是长度为 1 的堆栈,它就会成为您的标准迭代因子算法。

当然,有些递归函数不能写成尾递归格式。您仍然可以使用某种堆栈转换为迭代格式,但如果对空间复杂性有任何保证,我会感到惊讶。

【讨论】:

  • 这不是递归消除。当用非递归函数替换递归函数时,它应该使用有限的内存而不是理论上无限的堆栈。
  • “保证使用有限的内存”不是非递归函数的定义。它可能是一个理想的属性,但它不是一个定义要求。存在内存泄漏的迭代算法可能会使用无限量的内存,但没有人会争辩说仅此一项会使算法递归。
  • @jia3ep:这是递归消除。事实上,在某些情况下,通过将负载从操作系统的调用堆栈(在许多体系结构中空间有限)转移到堆中并避免函数调用的开销,它已经有了明显的好处。
  • 我认为这并没有改变递归的想法,您也可以以某种非堆栈方式植入虚拟机,并在其上运行您的递归代码,我在想更多的是不同算法的精神。好主意。
  • @Jimmy,硬件堆栈比使用堆快得多,所以堆栈模拟没有明显优势。
【解决方案2】:

作为对Ackermann function answer 的回应,这是一个非常简单的convert-the-call-stack-into-a-real-stack 问题。这也显示了迭代版本的一个好处。

在我的平台 (Python 3.1rc2/Vista32) 上,迭代版本计算 ack(3,7) = 1021 很好,而递归版本 stackoverflows。注意:它没有在另一台机器上的 python 2.6.2/Vista64 上进行 stackoverflow,所以它似乎是相当依赖于平台的,

(社区 wiki,因为这实际上是对另一个答案的评论[如果只有 cmets 支持代码格式....])

def ack(m,n):
  s = [m]
  while len(s):
     m = s.pop()
     if m == 0:
        n += 1 
     elif n == 0:
        s.append(m-1)
        n = 1
     else:
        s.append(m-1)
        s.append(m)
        n -= 1
  return n

【讨论】:

  • 我认为“追加”确实是“推送”?
  • 嗯,这是python中的列表呵呵。
【解决方案3】:

Ackermann function 不能在没有递归的情况下表达

编辑:如another answer 中所述,这是不正确的。

【讨论】:

  • 计数器:“阿克曼函数也可以使用康威链式箭头符号非递归地表示”(参见维基百科)
  • 有趣。 Ackermann 函数不是原始递归函数:en.wikipedia.org/wiki/Primitive_recursive_function。根据this page,具有基本算术,比较和有界for循环的语言无法表达这种类型的功能。但是如果你添加 WHILE 或 GOTO 你又是图灵完备的。
  • 我认为区分“数学”定义涉及递归形式的问题与只能递归实现计算的问题很重要。您可以使用非递归编程形式计算 Ackermann 函数 - 但是您存储每次迭代的状态。
  • @LBushkin 我完全同意
【解决方案4】:

您可以在没有递归的情况下定义Turing Machine(对吗?)所以语言不需要递归即可成为Turing-complete

【讨论】:

  • 在计算语言学中,图灵机高于基于堆栈的自动机,因为它可以模拟那些(条带可以用作堆栈)
【解决方案5】:

在编程中,递归实际上是迭代的一种特殊情况——您使用调用堆栈作为存储状态的一种特殊方式。您可以将任何递归方法重写为迭代方法。它可能更复杂或更不优雅,但它是等价的。

在数学中,有些问题需要递归技术才能得出答案 - 一些示例是求根 (Newton's Method)、计算素数、图优化等。然而,即使在这里,只是你如何区分术语“迭代”和“递归”的问题。

编辑:正如其他人指出的那样,存在许多定义是递归的函数 - 例如。 Ackermann function。然而,这并不意味着它们不能使用迭代构造来计算——只要你有一个图灵完备的操作集和无限的内存。

【讨论】:

    【解决方案6】:

    所有非 np 完全问题都可以通过序列、决策和迭代来解决。不需要递归,尽管它通常会大大简化问题。

    【讨论】:

    • NP完全的也可以。只是需要更长的时间。
    • 这个答案是错误的。不可判定的问题不是 NP 完全的(它们不能通过迭代或递归来解决)。
    【解决方案7】:

    这取决于解决问题需要多少行代码......

    使用递归列出 C:\ 上的所有文件,然后不使用。当然,您可以同时使用这两种方法 - 但其中一种方法会更易于理解和调试。

    【讨论】:

    • 在树中搜索可以是深度优先或广度优先。前者自然是用递归翻译的,而后者更自然地用迭代方法来实现。
    【解决方案8】:

    没有。递归只不过是一个堆栈,您可以通过显式实现堆栈来获得相同的结果。

    这可能不是一个特别令人满意的答案,但您必须提出更具体的问题才能获得更好的答案。例如,理论表明,在计算级别上,如果使用 while 循环与仅使用(传统的)for 循环,则可以解决的问题范围存在很大差异。

    我把“传统”放在那里,因为它们实际上是指迭代一定次数的循环,而 C 风格的 for (...;...;...) 循环是变相的 while 循环。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-07-22
      • 2021-12-06
      • 2015-10-25
      • 1970-01-01
      • 2016-03-12
      • 1970-01-01
      相关资源
      最近更新 更多