递归与堆栈

时间:2016-01-30 03:43:37

标签: algorithm recursion

我想知道递归是否是解决问题的唯一方法,那么迭代堆栈是唯一的其他解决方案吗?我认为它们是等价的:如果递归有效,那么迭代肯定会起作用,反之亦然。

此外,我不确定为什么递归被认为是低效的,并且经常导致堆栈溢出,而使用堆栈的迭代则不会。递归只是以用户看不到的自动方式使用堆栈。

2 个答案:

答案 0 :(得分:4)

虽然dancancodeanswer讨论了primitive recursive问题,recursive问题和recursively enumerable问题等不同类型的问题,但恕我直言这个问题更多的是关于直截了当recursioniteration

  

我想知道递归是否是问题的唯一解决方案,那么迭代堆栈是唯一的其他解决方案吗?

不,有很多不同的计算模型。然而,lambda calculus(递归的基础)和Turing machines(迭代的基础)是最流行的计算模型。另一种流行的计算模型是μ-recursion

什么是计算模型?

很长一段时间,数学家都想研究计算的本质。他们想知道哪些问题可以计算(即哪些问题有解决方案)以及哪些问题无法计算(即哪些问题没有解决方案)。他们还想知道计算的本质(例如,计算解决方案相对于其输入大小需要多长时间等)。

然而,只有一个问题:“计算”是一个非常抽象的术语。你怎么推理一些不具体的东西?这就是数学家需要一个他们可以推理的计算模型的原因。计算模型捕获了“计算的本质”。这意味着如果存在可以计算的问题,那么必须有一个算法在每个计算模型中计算它。

  

我认为它们是等价的:如果递归有效,那么迭代肯定会起作用,反之亦然。

是的,这是正确的。 Church-Turing thesis基本上表明每种计算模型的功效都相同。因此,使用递归(即lambda演算)可以做的所有事情也可以使用迭代(即图灵机)来完成。

事实上,世界上大多数计算机都基于图灵机。因此,每台计算机仅使用迭代。不过,您的计算机仍然可以执行递归程序。这是因为编译器会将递归程序转换为迭代机器代码。

  

此外,我不确定为什么递归被认为是低效的,并且经常导致堆栈溢出,而使用堆栈的迭代则不会。递归只是以用户看不到的自动方式使用堆栈。

这是因为操作系统处理进程的方式。大多数操作系统对堆栈的大小施加了最大限制。在我的Linux操作系统上,最大堆栈大小为8192 KB,这并不是很多。使用ulimit -s在POSIX兼容的操作系统上查找默认堆栈大小。这就是使用过多递归导致堆栈溢出的原因。

另一方面,在执行进程时(只要可用空间可用),可以动态增加堆的大小。因此,在使用迭代时(即使使用显式堆栈),您不必担心内存不足。

此外,递归通常比迭代慢,因为调用函数需要context switch,而在迭代中你只需要修改指令指针(即跳转,可能是条件)。

但是,这并不意味着迭代总是比递归更好。递归程序通常比迭代程序更小,更容易理解。此外,在某些情况下,编译器可以通过tail call optimization(TCO)完全消除上下文切换。这不仅使递归与迭代一样快,而且还确保堆栈大小不会增加。

有趣的是,通过将程序翻译成continuation-passing style(CPS),可以使所有递归程序都是尾递归的。因此,CPS和TCO可以完全eliminate the need of an implicit call stack。函数式编程语言的一些编译器和解释器在novel ways中使用此功能。

答案 1 :(得分:0)

有所谓的原始递归函数,可以用循环重写。然后有一类称为递归的函数,必须递归定义。最后一个类是递归可枚举的函数。

着名的Ackermann函数是递归的,但不是原始的递归函数。

在Computerphile youtube频道上有关于该主题的精彩视频: The Most Difficult Program to Compute?

递归肯定是有效的。考虑合并排序(msort.c)的glibc实现是递归的。

请注意,尾递归有一个常见的编译器优化,它将递归函数编译成循环。