Language agnostic 每个递归都能转换成迭代吗?

Language agnostic 每个递归都能转换成迭代吗?,language-agnostic,recursion,iteration,Language Agnostic,Recursion,Iteration,A提出了一个显然很有趣的问题: 尾部递归函数可以很容易地转换为迭代函数。其他的可以通过使用显式堆栈进行转换。每个递归都能转化为迭代吗 文章中的(计数器?)示例是: (define (num-ways x y) (case ((= x 0) 1) ((= y 0) 1) (num-ways2 x y) )) (define (num-ways2 x y) (+ (num-ways (- x 1) y) (num-ways x (- y 1))

A提出了一个显然很有趣的问题:

尾部递归函数可以很容易地转换为迭代函数。其他的可以通过使用显式堆栈进行转换。每个递归都能转化为迭代吗

文章中的(计数器?)示例是:

(define (num-ways x y)
  (case ((= x 0) 1)
        ((= y 0) 1)
        (num-ways2 x y) ))

(define (num-ways2 x y)
  (+ (num-ways (- x 1) y)
     (num-ways x (- y 1))

是的,显式地使用堆栈(但递归更容易阅读,IMHO)。

基本上是的,本质上,您最终要做的是将方法调用(隐式地将状态推送到堆栈上)替换为显式堆栈,以记住“上一次调用”到达的位置,然后执行“调用的方法”


我可以想象,通过基本上模拟方法调用,循环、堆栈和状态机的组合可以用于所有场景。一般来说,这是否会“更好”(更快,或在某种意义上更有效)还不太可能确定。

删除递归是一个复杂的问题,在定义明确的情况下是可行的。

以下案例属于简单案例:

  • 尾部递归

递归在实际解释器或编译器中实现为堆栈或类似结构。因此,您当然可以将递归函数转换为迭代对应函数,因为它总是这样做的(如果是自动的)。你只是在临时复制编译器的工作,可能是以一种非常丑陋和低效的方式。

从显式堆栈中可以看出,将递归转换为迭代的另一种模式是使用蹦床

在这里,函数要么返回最终结果,要么返回它本应执行的函数调用的闭包。然后,启动(蹦床)函数继续调用返回的闭包,直到达到最终结果

这种方法适用于相互递归的函数,但恐怕它只适用于尾部调用


这里有一个迭代算法:

def howmany(x,y)
  a = {}
  for n in (0..x+y)
    for m in (0..n)
      a[[m,n-m]] = if m==0 or n-m==0 then 1 else a[[m-1,n-m]] + a[[m,n-m-1]] end
    end
  end
  return a[[x,y]]
end

有时候,替换递归要容易得多。在20世纪90年代,递归曾经是CS中流行的东西,因此当时很多普通的开发人员认为,如果用递归解决某个问题,它是一个更好的解决方案。所以他们会使用递归,而不是反向循环,或者诸如此类愚蠢的事情。所以有时候删除递归是一个简单的“duh,那是显而易见的”类型的练习


现在这已经不是什么问题了,因为时尚已经转向了其他技术。

你总能把递归函数变成迭代函数吗?是的,绝对如此,如果记忆有用的话,丘奇-图灵理论证明了这一点。在lay术语中,它指出递归函数可以计算的是迭代模型(如图灵机)可以计算的,反之亦然。这篇论文并没有精确地告诉你如何进行转换,但它确实说这是绝对可能的

在许多情况下,转换递归函数很容易。Knuth在“计算机编程艺术”中提供了几种技术。通常,递归计算的对象可以用完全不同的方法在更少的时间和空间内计算。经典的例子是斐波那契数或其序列。你的学位计划中肯定遇到了这个问题

另一方面,我们当然可以想象一个如此先进的编程系统,它将公式的递归定义视为记忆先前结果的邀请,从而提供了速度优势,而无需麻烦地告诉计算机在使用递归定义计算公式时应遵循哪些步骤。迪克斯特拉几乎肯定想象过这样一个系统。他花了很长时间试图将实现与编程语言的语义分开。同样,他的非确定性和多处理编程语言在实践专业程序员之上

归根结底,许多函数很容易以递归形式理解、读取和写入。除非有令人信服的理由,否则您可能不应该(手动)将这些函数转换为显式迭代算法。您的计算机将正确处理该作业

我可以看到一个令人信服的理由。假设您有一个超高级语言的原型系统,如[donning石棉内衣]Scheme、Lisp、Haskell、OCaml、Perl或Pascal。假设条件是您需要用C或Java实现。(也许这是政治原因。)那么你当然可以递归地编写一些函数,但从字面上看,这些函数会破坏你的运行时系统。例如,Scheme中可以使用无限尾递归,但同样的习惯用法会给现有的C环境带来问题。另一个例子是使用词汇嵌套函数和静态作用域,Pascal支持,但C不支持


在这种情况下,您可以尝试克服对原文的政治抵制。您可能会发现自己重新实现Lisp时很糟糕,就像格林斯潘(开玩笑的)第十定律一样。或者,您可能会找到一种完全不同的解决方法。但无论如何,肯定有一种方法。

原则上,在一种对数据结构和调用堆栈都具有无限状态的语言中,总是可以删除递归并用迭代替换它。这是丘奇图灵理论的一个基本结果

给定一种实际的编程语言,答案就不那么明显了。问题在于,很可能存在这样一种语言,即程序中可以分配的内存量是有限的,但可以使用的调用堆栈量是无限的(32位C,其中堆栈变量的地址是不可访问的)。在这种情况下,递归更强大,因为它有更多的内存可以使用;没有足够的明确说明