我想知道while循环本质上是否是递归?
我认为这是因为while循环可以看作是最后调用自身的函数。如果不是递归,那有什么区别?
我想知道while循环本质上是否是递归?
我认为这是因为while循环可以看作是最后调用自身的函数。如果不是递归,那有什么区别?
Answers:
循环不是递归。实际上,它们是相反机制的主要示例:迭代。
递归的重点是处理的一个元素调用了自身的另一个实例。回路控制机构只是跳回到起点。
在代码中跳转并调用另一个代码块是不同的操作。例如,当您跳转到循环的开始时,循环控制变量仍具有与跳转前相同的值。但是,如果您调用所在例程的另一个实例,则新实例将具有其所有变量的新的,不相关的副本。实际上,一个变量可以在处理的第一层具有一个值,而在较低层具有另一个值。
此功能对于许多递归算法的工作至关重要,因此这就是为什么您无法通过迭代来模拟递归而又不管理跟踪所有这些值的调用框架堆栈的原因。
这取决于您的观点。
如果您看一下可计算性理论,那么迭代和递归是同等表达的。这意味着您可以编写一个计算某些内容的函数,而无论递归还是迭代都无所谓,您将可以选择这两种方法。没有什么可以递归计算的,不能递归计算,反之亦然(尽管程序的内部工作方式可能有所不同)。
许多编程语言不会将递归和迭代视为相同,这是有充分理由的。通常,递归意味着语言/编译器会处理调用堆栈,而迭代意味着您可能必须自己进行堆栈处理。
但是,在某些语言(尤其是函数式语言)中,诸如循环(for,while)之类的东西实际上只是递归的语法糖,并以这种方式在幕后实现。这在函数式语言中通常是合乎需要的,因为它们通常不具有循环的概念,并且添加它会使其演算变得更加复杂,而没有任何实际原因。
所以不,它们本质上并不相同。它们具有同等的表现力,这意味着您不能迭代地计算某些内容,也不能递归地进行计算,反之亦然,但这就是一般情况下的情况(根据Church-Turing论文)。
请注意,我们在这里谈论的是递归程序。还有其他形式的递归,例如在数据结构(例如树)中。
如果从实现的角度来看它,那么递归和迭代几乎是不一样的。递归为每个调用创建一个新的堆栈框架。递归的每一步都是独立的,从被调用者(自己)获取用于计算的参数。
另一方面,循环不会创建呼叫帧。对于他们来说,上下文不会在每个步骤中保留。对于循环,程序仅跳回到循环的开始,直到循环条件失败为止。
要知道这一点非常重要,因为它可以在现实世界中产生巨大的差异。为了进行递归,必须在每次调用时保存整个上下文。对于迭代,您可以精确控制哪些变量在内存中以及什么保存在哪里。
如果您这样看,您很快就会发现,对于大多数语言而言,迭代和递归在本质上是不同的,并且具有不同的属性。根据情况,某些特性比其他特性更理想。
递归可以使程序更简单,更易于测试和证明。将递归转换为迭代通常会使代码更复杂,从而增加失败的可能性。另一方面,转换为迭代并减少调用堆栈帧的数量可以节省大量的内存。
区别在于隐式堆栈和语义。
“结束时调用自身”的while循环在完成后没有要爬回的堆栈。它的最后一次迭代设置了结束时的状态。
但是,如果没有这个隐式堆栈来记住以前完成的工作状态,就无法进行递归。
的确,如果您明确允许迭代访问堆栈,则可以解决任何递归问题。但是那样做是不一样的。
语义上的差异与以下事实有关:查看递归代码传达的想法与迭代代码完全不同。迭代代码一次完成一个步骤。它接受以前产生的任何状态,并且只能创建下一个状态。
递归代码将问题分解为分形。这小部分看起来像是大部分,因此我们可以用相同的方式来完成其中的一部分。这是思考问题的另一种方式。它非常强大,需要习惯。在几行中可以说很多。即使它可以访问堆栈,也无法从while循环中删除它。
这完全取决于您对术语的内在使用。在编程语言级别,它们在语法和语义上是不同的,并且它们在性能和内存使用上也有很大差异。但是,如果您对理论进行了足够深入的研究,它们可以彼此定义,因此在某种理论意义上是“相同的”。
真正的问题是:什么时候区分迭代(循环)和递归,什么时候将其视为相同的东西有用?答案是,在实际编程时(与编写数学证明相对),区分迭代和递归很重要。
递归创建一个新的堆栈框架,即每个调用都使用一组新的局部变量。这会产生开销,并占用堆栈上的空间,这意味着足够深的递归可能会使堆栈溢出,从而导致程序崩溃。另一方面,迭代仅修改现有变量,因此通常更快,并且仅占用恒定的内存量。因此,这对于开发人员而言是非常重要的区别!
在具有尾调用递归的语言(通常是功能语言)中,编译器可能能够以仅占用固定数量内存的方式优化递归调用。在这些语言中,重要的区别不是迭代与递归,而是非尾调用递归版本的尾调用递归和迭代。
底线:您需要能够分辨出差异,否则您的程序将崩溃。
while
循环是递归的一种形式,例如参见对此问题的公认答案。它们在可计算性理论中对应于μ运算符(例如,请参见此处)。
for
在一定范围的数字,有限集合,数组等上迭代的循环的所有变体都对应于原始递归,请参见此处和此处。请注意,for
C,C ++,Java等while
循环实际上是循环的语法糖,因此它不对应于原始递归。Pascal for
循环是原始递归的一个示例。
一个重要的区别是原始递归总是终止,而广义递归(while
循环)可能不会终止。
编辑
关于注释和其他答案的一些说明。“当根据事物本身或事物类型定义事物时,就会发生递归。” (请参阅Wikipedia)。所以,
while循环本质上是递归吗?
由于您可以while
根据自身定义循环
while p do c := if p then (c; while p do c))
那么,是的,while
循环是递归的一种形式。递归函数是递归的另一种形式(递归定义的另一个示例)。列表和树是其他形式的递归。
许多答案和评论暗含的另一个问题是
while循环和递归函数是否等效?
这个问题的答案是否定的:while
循环对应于尾递归函数,其中循环访问的变量对应于隐式递归函数的参数,但是,正如其他人指出的那样,非尾递归函数如果不while
使用额外的堆栈,则无法通过循环建模。
因此,“ while
循环是递归的一种形式”这一事实与“某些递归函数不能由while
循环表示”的事实并不矛盾。
FOR
循环的语言才能计算出所有原始递归函数,而只有一个WHILE
循环的语言可以计算出所有µ递归函数(事实证明,µ递归函数正是那些图灵机可以计算)。或者,简而言之:原始递归和µ递归是数学/可计算性理论的技术术语。
的确,递归和无界while循环在计算表达性方面是等效的。也就是说,可以递归地编写任何程序,而可以使用循环将其重写为等效程序,反之亦然。两种方法都是图灵完备的,即可以用来计算任何可计算函数。
在编程方面的根本区别在于,递归使您可以利用存储在调用堆栈中的数据。为了说明这一点,假设您想使用循环或递归来打印单链接列表的元素。我将使用C作为示例代码:
typedef struct List List;
struct List
{
List* next;
int element;
};
void print_list_loop(List* l)
{
List* it = l;
while(it != NULL)
{
printf("Element: %d\n", it->element);
it = it->next;
}
}
void print_list_rec(List* l)
{
if(l == NULL) return;
printf("Element: %d\n", l->element);
print_list_rec(l->next);
}
简单吧?现在,我们进行一些修改:以相反的顺序打印列表。
对于递归变量,这是对原始函数的几乎微不足道的修改:
void print_list_reverse_rec(List* l)
{
if (l == NULL) return;
print_list_reverse_rec(l->next);
printf("Element: %d\n", l->element);
}
对于循环功能,我们有一个问题。我们的清单是单链的,因此只能向前浏览。但是由于我们要反向打印,所以我们必须开始打印最后一个元素。一旦到达最后一个元素,就无法再回到倒数第二个元素。
因此,我们要么必须进行大量重新遍历,要么必须构建一个辅助数据结构来跟踪所访问的元素,然后可以从中高效打印。
为什么我们的递归没有这个问题?因为在递归中,我们已经有一个辅助数据结构:函数调用堆栈。
由于递归允许我们返回到递归调用的上一个调用,而该调用的所有局部变量和状态仍保持不变,因此我们获得了一些灵活性,在迭代情况下进行建模很麻烦。
循环是实现特定任务(主要是迭代)的一种特殊形式的递归。可以用几种语言以相同的性能[1]以递归样式实现循环。在SICP [2]中,您可以看到for循环被描述为“合成糖”。在大多数命令式编程语言中,for和while块使用与其父函数相同的作用域。但是,在大多数函数式编程语言中,因为不需要循环,所以不存在for循环或while循环。
命令式语言具有for / while循环的原因是它们通过使状态变化来处理状态。但是实际上,如果您从不同的角度看待,如果您将while块视为一个函数本身,则需要对其进行参数处理,处理并返回新的状态-这也可能是同一函数具有不同参数的调用-可以将循环视为递归。
世界也可以定义为可变或不可变的。如果我们将世界定义为一组规则,并调用一个接受所有规则的终极函数,并将当前状态作为参数,然后根据这些具有相同功能的参数返回新状态(在同一状态下生成下一个状态方式),我们也可以说这是递归和循环。
在下面的示例中,life是函数采用两个参数“ rules”和“ state”,并且在下一次滴答中将构造新的状态。
life rules state = life rules new_state
where new_state = construct_state_in_time rules state
[1]:尾部调用优化是函数式编程语言中的常见优化,它在递归调用中使用现有函数堆栈,而不是创建新函数。
[2]:麻省理工学院计算机程序的结构和解释。https://mitpress.mit.edu/books/structure-and-interpretation-computer-programs
while循环与递归不同。
调用函数时,将发生以下情况:
堆栈框架已添加到堆栈中。
代码指针移到函数的开头。
当while循环结束时,将发生以下情况:
条件询问是否为真。
如果是这样,代码会跳到一个点。
通常,while循环类似于以下伪代码:
if (x)
{
Jump_to(y);
}
最重要的是,递归和循环具有不同的汇编代码表示形式和机器代码表示形式。这意味着它们不相同。它们可能具有相同的结果,但是不同的机器代码证明它们不是100%同一件事。
仅仅迭代不足以等同于递归,但是使用堆栈的迭代通常等效。可以将任何递归函数重新编程为带有堆栈的迭代循环,反之亦然。但是,这并不意味着它是实用的,并且在任何特定情况下,一种或另一种形式可能比另一种形式具有明显的优势。
我不确定为什么会引起争议。堆栈的递归和迭代是相同的计算过程。可以说,它们是相同的“现象”。
我唯一能想到的是,当将它们视为“编程工具”时,我同意您不应将它们视为同一件事。它们在数学上或计算上都是等效的(同样是使用堆栈进行迭代,而不是通常进行迭代),但这并不意味着您应该以任何一个都可以做到的思想来对待问题。从实现/问题解决的角度来看,某些问题可能以一种或两种方式更好地解决问题,而作为程序员的工作是正确地确定哪个更适合。
为了澄清,问题的答案是while循环本质上是递归吗?是一个肯定的no,或者至少是“除非您也有堆栈”否则。