消除递归-幕后理论研究


10

我是这个网站的新手,这个问题肯定不是研究水平-很好。我在软件工程方面有一点背景,但是在CSTheory中几乎没有背景,但是我觉得它很有吸引力。长话短说,如果这个问题在本网站上可以接受,我想对以下内容提供更详细的答案。

因此,我知道每个递归程序都有一个迭代模拟,并且通过维护类似于“系统堆栈”的内容并推送环境设置(如返回地址等),我有点理解为它提供的流行解释。 。

更具体一点,我想(正式地)看看如果您有一个函数调用链如何证明这一说法。此外,如果存在一些可能导致调用某些条件语句该怎么?也就是说,潜在函数调用图具有一些紧密连接的组件。F i F jF0F1个F一世F一世+1个FñF0F一世FĴ

我想知道如何让我们说一些递归的迭代转换器来处理这些情况。我前面提到的手工波浪形说明是否真的足以解决这个问题?我的意思是为什么在某些情况下删除递归很容易。特别是从二叉树的遍历遍历中删除递归确实很容易-这是一个标准的面试问题,但是对后继订单而言,删除递归一直是我的噩梦。

我真正要问的是问题2

(1)确实有更正式(令人信服的)证明可以将递归转换为迭代吗?

(2)如果该理论真的存在,那么为什么我会发现例如简化订购的迭代而使订购如此困难呢?(除了我的智力有限)


1
像iteratizing这个词一样:)
Akash Kumar

我不确定我是否完全理解,但是如果递归确实在某处结束,那么您实际上可以使用自己的堆栈来模拟系统堆栈。对于第(2)部分,这些问题在计算复杂度方面没有不同。
singhsumit 2012年

我认为这个问题最适合尚未上线的计算机科学站点。至于第二个问题,您能否详细说明为什么您认为比较困难?该过程应几乎相同。
拉斐尔2012年

感谢大家的评论-猜猜我已经做了很多阅读工作。
Itachi Uchiha 2012年

@Raphael-关于为什么我认为重复邮购很难的一条评论(除了我无法做到)。我阅读了一些有关删除递归的文章,并遇到了称为尾递归函数的内容。事实证明,它们更容易迭代。我仍然没有正式理解为什么这是真的。但还有另一件事我应该补充。我听说迭代邮购需要两个堆栈,而不是一个,但是不知道细节。现在我迷路了-为什么这两种遍历模式之间存在这种差异?为什么尾部递归容易处理?
Itachi Uchiha 2012年

Answers:


6

如果我理解正确,那么您将转换为仅包含其他函数调用的函数就变得很清楚。

因此,假设我们有一个“调用链”。如果我们进一步假设本身不是递归的(因为我们已经转换了它们),我们可以将所有这些调用内联到的定义中,从而成为我们已经可以处理的直接递归函数。F 1F n FFF1个FñFF1个FñF

如果某些本身具有发生的递归调用链,即。在这种情况下,我们可以相互递归,这需要另一个技巧来摆脱。这个想法是同时计算两个函数。例如,在普通情况下: F F jF F jFĴFFĴFFĴ

f(0) = a
f(n) = f'(g(n-1))

g(0) = b
g(n) = g'(f(n-1))

f'g'非递归函数(或至少独立于fg)变为

h(0) = (a,b)
h(n) = let (f,g) = h(n-1) in (f'(g), g'(f)) end

f(n) = let (f, _) = h(n) in f end
g(n) = let (_, g) = h(n) in g end

这自然会扩展到涉及的更多功能和更复杂的功能。


很高兴我能帮上忙。请记住,单击旁边的对勾以接受您喜欢的答案。
拉斐尔

1
Raphel,您的技巧仅在两个递归函数都接受相同类型的参数时才有效。如果f并且g接受不同类型的类型,则需要一个更通用的技巧。
Andrej Bauer

@AndrejBauer很好的观察,我完全错过了。我真的很喜欢拉斐尔的方法,但是就像您在一般情况下观察到的那样,我们可能需要一些不同的想法。您还能提出其他建议吗?
Itachi Uchiha 2012年

fgñ-1个ñ-2

好吧,请参阅我的答案。
安德烈·鲍尔

8

是的,有令人信服的理由相信递归可以转化为迭代。这是每个编译器在将源代码转换为机器语言时所做的事情。从理论上讲,您应该遵循Dave Clarke的建议。如果您希望看到将递归转换为非递归代码的实际代码,请machine.ml在我的PL Zoo中以MiniML语言查看(请注意,loop底部的实际运行代码的函数是尾递归的,因此可以轻松地转换为实际循环)。

还有一件事。MiniML不支持相互递归函数。但这不是问题。如果函数之间有相互递归

F1个一个1个1个
F2一个22
Fñ一个ññ

可以使用单个递归映射来表示递归

F一个1个++一个ñ1个++ñ

8

您可能想看看SECD机器。一种功能语言(尽管它可以是任何一种语言)都被翻译成一系列指令,这些指令管理诸如放置堆栈参数,“调用”新功能等之类的事情,所有这些都由一个简单的循环进行管理。
递归调用实际上从未被调用。而是将要调用的函数主体的指令放在堆栈上以运行。

一种相关的方法是CEK机器

这些都已经存在很长时间了,因此有很多工作要做。当然,有证据表明它们可以正常工作,并且将程序“编译”为SECD指令的过程在程序大小上是线性的(不必考虑程序)。

我的回答的重点是,有一个自动执行所需操作的过程。不幸的是,这种转换不一定是程序员容易理解的事情。我认为关键在于,当您要迭代程序时,需要在堆栈中存储从迭代函数调用(称为延续)返回时程序需要执行的操作。对于某些函数(例如尾递归函数),延续是微不足道的。对于其他人,延续可能非常复杂,尤其是如果您必须自己编码时。


我会在这里诚实。我真的很想了解为什么(以及如何)可以迭代每个递归程序。但是我很难阅读论文-我通常无法访问它们。我的意思是,我想要一个比我在问题中提到的“ handwavy”描述更深刻的理由。但是我也很高兴能为我带来一些新的见解-它并不一定是其具体细节的完整证明
Itachi Uchiha 2012年

[cntd]我的意思是,如果有证据,我会喜欢的,告诉我为什么迭代一个程序比另一个程序容易。但是从某种意义上说,无论采用哪种递归程序作为输入,递归到迭代转换器都应该起作用。不确定,但我想制造这样的转换器可能和停止问题一样难?我只是在这里猜测-但我希望递归到迭代转换器的存在,如果可以,我希望它解释对不同递归程序进行迭代的内在复杂性。不确定,但是我应该编辑问题吗?我的问题清楚吗?
Itachi Uchiha 2012年

@ItachiUchiha-我认为您的问题无法确定。看看安德烈·鲍尔(Andrej Bauer)的答案。他指出,每个编译器都会在将源代码转换为机器语言时执行此操作。他还补充说,您可以看到使用MiniM(a)l语言将递归转换为非递归的实际代码。这清楚地表明,存在用于“迭代”递归的决策程序。我不确定删除递归的固有(概念上)难度/复杂性。我对这个问题不太清楚,但看起来很有趣。也许您可以编辑问题以得到更好的答复
Akash Kumar 2012年

我的回答的重点是,有一个自动执行所需操作的过程。不幸的是,这种转换不一定是程序员容易理解的事情。我认为关键在于,当您要迭代程序时,需要在堆栈中存储从迭代函数调用(称为延续)返回时程序需要执行的操作。对于某些函数(例如尾递归函数),延续是微不足道的。对于其他人,延续可能非常复杂,尤其是如果您必须自己编码时。
戴夫·克拉克

6

“确实存在更正式(令人信服的)证明可以将递归转换为迭代吗?”

:图灵机的图灵完整性:-)

开玩笑的是,图灵等效的随机存取存储程序(RASP)机器模型接近于实际微处理器的工作方式,并且其指令集仅包含条件跳转(无递归)。对代码进行动态自我修改的可能性使实现子例程和递归调用的任务更加容易。

我认为您可以找到许多有关“ 递归到迭代转换 ”的文章/文章(请参阅Dave的答案,或者只是Google的关键词),但也许鲜为人知实用)的方法是有关递归算法的硬件实现的最新研究(使用直接“编译”为硬件的VHDL语言)。例如,参见V.Sklyarov的论文“ 基于FPGA的递归算法的实现 ”(该论文提出了一种在硬件中实现递归算法的新方法。....研究了递归算法在数据排序和压缩领域的两个实际应用详细...)。


1

如果您熟悉支持lambda的语言,那么一种方法是研究CPS转换。完全取消CPS转换的用途,而不必使用调用堆栈(尤其是递归)。它将包含过程调用的程序转换为仅包含尾部调用的程序(您可以将它们视为gotos,这是一个迭代构造)。

CPS转换与将调用堆栈显式保留在传统的基于数组的堆栈中密切相关,但是调用数组由链接的闭包表示,而不是在数组中。


0

在我看来,这个问题可以追溯到计算定义的起源,并且早在那段时间就被严格地证明了,当时教堂的lambda演算(高度反映了递归的概念)被证明等同于图灵机,并且包含在其中。在仍然使用的术语“递归语言/功能”中。显然,沿这些行的后续关键参考如下

正如Peter Landin在1965年发表的论文《 ALGOL 60与丘奇的Lambda表示法之间的对应关系》所指出的那样,可以使用lambda演算来理解顺序过程编程语言,它提供了过程抽象和过程(子程序)应用的基本机制。

关于这一点的很多知识都在这个维基百科页面教堂图腾论文中。我不确定确切的细节,但维基百科的文章似乎表明,是Rosser(1939)首次证明了lambda演算与图灵机之间的等效性。也许/大概他的论文具有类似栈的机制,可以将(可能是递归的)lambda调用转换为tm结构?

罗瑟,JB(1939)。“戈德尔定理和教会定理的证明的非正式说明”。《符号逻辑杂志》(《符号逻辑杂志》,第4卷,第2期)4(2):53–60。doi:10.2307 / 2269059。JSTOR 2269059。

当然,对于对现代Lisp语言和变体Scheme的原理感兴趣的任何人,请注意与lambda演算有很大的相似之处。通过研究解释器代码以进行表达评估,可以得出最初包含在Lambda演算的图谱完整性中的思想。


1
图灵/λ等价证明在本文的附录中:www.cs.virginia.edu/~robins/Turing_Paper_1936.pdf
Radu GRIGore 2012年
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.