迭代可以代替递归吗?


42

我一直看到堆栈溢出,例如hereherehereherehere以及其他我不愿提及的其他内容,“任何使用递归的程序都可以仅通过迭代转换为程序”。

甚至还有一个非常upvoted 线程具有高度upvoted 答案是肯定的说,这是可能的。

现在我不是说他们错了。只是这个答案抵消了我对计算的微薄知识和理解。

我相信每个迭代功能都可以表示为递归,而维基百科对此有一个陈述。但是,我怀疑相反是真的。首先,我怀疑非原始递归函数是否可以迭代表示。

我也怀疑超级运算是否可以迭代表示。

在对我的问题 @YuvalFIlmus的回答(我不明白)中,他说不可能将任何数学运算序列转换为一系列加法运算。

如果YF的答案确实是正确的(我想是的,但是他的推理超出了我的脑袋),那么这是否意味着并非每个递归都可以转换为迭代?因为如果有可能将每个递归转换为迭代,我将能够将所有操作表示为一系列加法。

我的问题是这样的:

可以将每个递归转换为迭代吗,为什么?

请给一个聪明的高中生一个答案,或者本科一年级的学生会理解。谢谢。

PS我不知道什么是原始递归(我确实知道Ackermann函数,它不是原始递归,但仍然可以计算。我对它的了解也来自Ackermann函数的Wikipedia页面。)

PPS:如果答案是肯定的,例如您可以编写非原始递归函数的迭代版本。例如,答案是Ackermann。它会帮助我理解。


21
您在CPU上运行的所有操作都是迭代的。您可能会以更高阶的语言递归地编写它,但是无论如何它都会被编译成一堆迭代的汇编程序指令。因此,从字面上看,编译器完全按照您的要求进行操作,它将所有您喜欢的递归转换为一串针对CPU的迭代指令。
达沃

6
在低层次上,我们大多数人都知道递归等于迭代加堆栈。上下文无关的语法模型递归,而下推自动机则是带有堆栈内存的迭代“程序”。
Hendrik

2
只是指出,通常最好等待至少24小时,看看是否弹出其他答案。坦率地说,已接受的问题对我来说似乎相当冗长且令人费解,并且似乎故意使事情变得不必要。您问题的基本答案是“用于递归的堆栈可以以迭代的方式显式实现”,并且不需要比这复杂得多。关于内存不受限制或不受限制的考虑不会起作用,因为无论如何,递归堆栈也必须使用该功能。
AnoE

只需迭代即可实现Turing机器仿真器
Sarge Borsch

在很久以前的一门比较语言课程中,我们不得不用FORTRAN(不是现代的Fortran)和一种选择的递归语言来编写Ackermann的汇编函数。是的,在实践中是可能的。当然,从理论上讲也是有可能的。例如,请参见问题证明图灵机和lambda演算是等效的
大卫·哈门

Answers:


52

可以通过迭代加无限内存来代替递归。

如果您只有迭代(例如while循环)和有限数量的内存,那么您所拥有的只是有限的自动机。有了有限的内存,计算就可以包含有限数量的可能步骤,因此可以使用有限的自动机来模拟它们。

拥有无限的内存会改变这笔交易。这种无限制的内存可以采用多种形式,这些形式具有同等的表现力。例如,图灵机使它保持简单:只有一个磁带,并且计算机一次只能在磁带上向前或向后移动一步,但是这足以执行递归功能。

图灵机可以看作是计算机(有限状态机)的理想模型,其中有一些额外的存储,可以按需增长。请注意,至关重要的是,不仅磁带上没有有限的界限,而且即使给出输入,也无法可靠地预测将需要多少磁带。如果您可以预测(即计算)从输入中需要多少磁带,那么您可以通过计算最大磁带大小,然后将整个系统(包括现在的有限磁带)视为有限状态机,来决定是否停止计算。

用计算机模拟图灵机的另一种方法如下。用计算机程序模拟图灵机,该计算机程序将磁带的开头存储在内存中。如果计算到达磁带中适合内存的那部分的末尾,请用更大的计算机代替计算机,然后再次运行计算。

现在假设您想用计算机来模拟递归计算。执行递归函数的技术是众所周知的:每个函数调用都有一块内存,称为栈帧。至关重要的是,递归函数可以通过传递变量来通过多个调用传播信息。就计算机上的实现而言,这意味着函数调用可以访问(大)*父调用的堆栈框架。

计算机是处理器-有限状态机(具有大量状态,但是我们在这里进行计算理论,因此重要的是它是有限的)-加上有限的内存。微处理器运行一个巨大的while循环:“在通电时,从内存中读取一条指令并执行它”。(真正的处理器要比这复杂得多,但是它不会影响它们的计算能力,只会影响它们执行的速度和便捷性。)计算机可以仅通过while循环执行递归函数以提供迭代,以及执行以下操作的机制:访问内存,包括随意增加内存大小的能力。

如果将递归限制为原始递归,则可以将迭代限制为有界迭代。也就是说,除了使用运行时间无法预测的while循环外,您还可以将for循环用于在循环开始时已知迭代次数的循环¹。迭代次数可能在程序开始时未知:它本身可以由先前的循环计算得出。

我什至不打算在这里画出一个证明,但是从原始递归到完全递归与从for循环到while循环之间存在一种直观的关系:在两种情况下,这都涉及到事前不知道停。对于完全递归,这是通过最小化运算符完成的,在该过程中一直进行下去,直到找到满足条件的参数为止。使用while循环时,可以一直进行直到满足循环条件为止。

¹ 在循环C的语言可以进行无限次迭代一样,它只是一个约定的事情给他们限制在有限的迭代。当人们在计算理论上谈论“ for循环”时,这意味着仅计数从1到n(或等效值)的循环 forwhilen


接受详细说明,并保持在要求的水平。
Tobi Alafin '16

12
我认为值得一提的是,与实际计算机相比,递归还会占用内存(不断增长的调用堆栈)。因此,在实际应用中,不需要无限制的内存(因为所有内容均受其限制)。递归通常比迭代需要更多的内存。
Agent_L

@Agent_L是的,递归需要无限制的内存来存储所有堆栈帧。使用递归方法时,需要无限制的内存,但是从直观上讲,它无法与操作序列(如迭代)分开。
吉尔(Gilles)'所以

1
也许有趣的是“ Turn-Turing”论文。图灵机是高度迭代的机器,没有递归的概念。Lambda演算是一种旨在以递归方式表示计算的语言。Church-Turing论文表明,所有lambda表达式都可以在Turing机器上进行评估,将递归和迭代联系在一起。
科特·阿蒙

1
@BlackVegetable如果递归方法没有内部变量,并且它使用的唯一内存是调用堆栈(可以通过TCO优化的内存),则它的迭代版本很可​​能也没有内部变量,并且仅使用固定数量的内存(变量)在所有迭代中共享,例如计数器。
Agent_L '16

33

每次递归都可以转换为迭代,就像您的CPU所见证的那样,CPU使用提取执行无限迭代来执行任意程序。这是Böhm-Jacopini定理的一种形式。而且,许多图灵完备的计算模型没有递归,例如图灵机计数器机

原始递归函数与使用有限迭代的程序相对应,也就是说,您必须预先指定循环执行的迭代次数。有界迭代通常无法模拟递归,因为Ackermann函数不是原始递归。但是无界迭代可以模拟任何部分可计算的函数。


25

a(n,m)

s

push(s,x)xxpop(s)empty(s)

Ackermann(n0,m0):

  • s= (初始化空堆栈)
  • push(s,n0)
  • push(s,m0)
  • while(true):
    • mpop(s)
    • if(empty(s)):
      • return m (结束循环)
    • npop(s)
    • if(n=0):
      • push(s,m+1)
    • else if(m=0):
      • push(s,n1)
      • push(s,1)
    • else:
      • push(s,n1)
      • push(s,n)
      • push(s,m1)

我在Ceylon中实现了该功能,您可以根据需要在WebIDE中运行它。(它在循环的每次迭代开始时输出堆栈。)

当然,这只是将隐式调用堆栈从递归移动到带有参数和返回值的显式堆栈中。


15
我认为这是重要的一点。您已经非常明确地显示了递归没有什么特别的,并且可以通过自己跟踪调用栈,而不是让编译器来执行,可以将其删除。递归只是一种编译器功能,使编写此类程序更加容易。
大卫·里希比

4
感谢您花大力气编写所需的程序。
Tobi Alafin

16

已经有了一些不错的答案(我什至不希望与之竞争),但是我想提出这个简单的解释。

递归只是对运行时堆栈的操作。递归添加一个新的堆栈框架(用于递归函数的新调用),而返回则删除一个堆栈框架(用于递归函数的刚刚完成的创新)。递归将导致添加/删除一定数量的堆栈帧,直到最终它们全部退出(希望如此)并将结果返回给调用者。

现在,如果您创建了自己的堆栈,用推入堆栈替换了递归函数调用,用弹出堆栈替换了从递归函数返回的内容,并且有一个while循环一直运行到堆栈为空,那会发生什么?


2

据我所知,以我自己的经验,您可以将任何递归实现为迭代。如上所述,递归使用堆栈,该堆栈在概念上是无限的,但实际上是有限的(您是否收到过堆栈溢出消息?)。在编程的早期(上个世纪的上个世纪第三季度),我使用的是实现递归算法的非递归语言,没有任何问题。我不确定如何证明这一点。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.