递归与迭代


109

说在所有使用递归的地方for都可以使用循环是正确的吗?如果递归通常较慢,那么在for循环迭代中使用递归的技术原因是什么?

并且如果总是有可能将递归转换为for循环,是否有经验法则?


3
recursioniterationiteration = for loop我认为。
gongzhitaao

4
Tom Moertel的博客上有四个关于将递归代码转换为迭代代码的出色文章:blog.moertel.com/tags/recursion.html
cjohnson318

Answers:


148

递归通常要慢得多,因为所有函数调用必须存储在堆栈中,以允许返回到调用者函数。在许多情况下,必须分配和复制内存以实现范围隔离。

某些优化(如尾部调用优化)可使递归更快,但并非总是可能的,并且并非所有语言都实现。

使用递归的主要原因是

  • 当它模仿我们对问题的处理方法时,它在许多情况下更加直观
  • 某些数据结构(例如树)更易于使用递归进行探索(或者在任何情况下都需要堆栈)

当然,每个递归可以建模为一种循环:这就是CPU最终将要做的事情。递归本身更直接地意味着将函数调用和作用域放在堆栈中。但是将递归算法更改为循环算法可能需要大量工作,并且会使代码的可维护性降低:至于每次优化,都应仅在某些分析或证据表明有必要时才尝试使用它。


10
补充说一下,递归与归约术语密切相关,归约术语在许多算法和CS中都起着核心作用。
SomeWittyUsername 2013年

3
您能否提供一个递归使代码更易于维护的示例?以我的经验,总是相反。谢谢
Yeikel '18

@Yeikel编写一个f(n)返回第n个斐波纳契数的函数。
马特

54

说在各处使用递归都可以使用for循环是否正确?

是的,因为大多数CPU中的递归都是使用循环和堆栈数据结构建模的。

如果递归通常比较慢,那么使用它的技术原因是什么?

它不是“通常较慢”:递归错误地应用较慢。最重要的是,现代编译器擅长将某些递归转换为循环,而无需询问。

并且如果总是有可能将递归转换为for循环,是否有经验法则?

编写迭代程序,以便在迭代解释时能更好地理解算法;为算法编写递归程序,最好以递归方式进行解释。

例如,经常以递归方式解释搜索二叉树,运行快速排序以及解析许多编程语言中的表达式。这些也是最好的递归编码。另一方面,就阶乘而言,计算阶乘和计算斐波纳契数更容易解释。使用递归对他们来说就像是用大锤乱打苍蝇:这不是一个好主意,即使在大锤它做了很好的工作+


+我借用了迪杰斯特拉(Dijkstra)的“编程学科”中的大锤类比。


7
由于创建了堆栈框架等,递归通常更昂贵(内存更慢/更多)。如果将其正确应用到足够复杂的问题上,则差异可能很小,但代价仍然更高。可能有例外,例如尾部递归优化。
Bernhard Barker 2013年

我不确定每种情况下是否有一个for循环。考虑更复杂的递归或具有多个变量的递归
SomeWittyUsername

@dasblinkenlight从理论上讲,可以将多个循环减少到一个循环,但是对此不确定。
SomeWittyUsername 2013年

@icepack是的,有可能。可能不是很漂亮,但是有可能。
Bernhard Barker

我不确定我是否同意你的第一句话。CPU本身根本不对递归建模,因为递归是在CPU上运行的指令。其次,循环结构(没有必要)具有动态增长和收缩的数据集,其中递归算法通常将对递归必须执行的每个深度进行递归。
trumpetlicks 2013年

28

题 :

如果递归通常较慢,那么将其用于循环迭代的技术原因是什么?

答:

因为在某些算法中很难迭代求解。尝试以递归和迭代的方式解决深度优先搜索。您会发现很难用迭代来解决DFS。

可以尝试的另一件好事:尝试编写Merge进行迭代排序。这将花费您很多时间。

题 :

说在各处使用递归都可以使用for循环是否正确?

答:

是。这个线程对此有很好的答案。

题 :

并且如果总是有可能将递归转换为for循环,是否有经验法则?

答:

相信我。尝试编写自己的版本以迭代解决深度优先搜索。您会注意到一些问题更容易递归解决。

提示:当您解决可以通过分治法解决的问题时,递归非常有用


3
我感谢尝试提供权威的答案,并且我敢肯定作者很聪明,但是“信任我”对有意义的问题却没有帮助,因为这个问题的答案并不明显。有非常简单的算法可以进行深度优先的迭代搜索。有关伪代码中算法的说明,请参
见此

3

除了速度较慢之外,递归还可能导致堆栈溢出错误,具体取决于递归的深度。


3

要使用迭代编写等效方法,我们必须显式使用堆栈。迭代版本的解决方案需要堆栈的事实表明该问题非常困难,可以从递归中受益。通常,递归最适合无法使用固定内存量解决的问题,因此在迭代解决时需要堆栈。话虽如此,递归和迭代在遵循不同的模式时可以显示相同的结果。根据情况确定哪种方法更好,最佳实践是根据问题遵循的模式进行选择。

例如,要找到Triangular序列的第n个三角数:1 3 6 10 15…一个使用迭代算法找到第n个三角数的程序:

使用迭代算法:

//Triangular.java
import java.util.*;
class Triangular {
   public static int iterativeTriangular(int n) {
      int sum = 0;
      for (int i = 1; i <= n; i ++)
         sum += i;
      return sum;
   }
   public static void main(String args[]) {
      Scanner stdin = new Scanner(System.in);
      System.out.print("Please enter a number: ");
      int n = stdin.nextInt();
      System.out.println("The " + n + "-th triangular number is: " + 
                            iterativeTriangular(n));
   }
}//enter code here

使用递归算法:

//Triangular.java
import java.util.*;
class Triangular {
   public static int recursiveTriangular(int n) {
      if (n == 1)
     return 1;  
      return recursiveTriangular(n-1) + n; 
   }

   public static void main(String args[]) {
      Scanner stdin = new Scanner(System.in);
      System.out.print("Please enter a number: ");
      int n = stdin.nextInt();
      System.out.println("The " + n + "-th triangular number is: " + 
                             recursiveTriangular(n)); 
   }
}

1

大多数答案似乎都假定iterative= for loop。如果您的for循环不受限制(la C,您可以使用循环计数器执行任何操作),那么这是正确的。如果它是真实的 for循环(例如,在Python或大多数功能性语言中,您无法手动修改循环计数器),那么它是正确的。

所有(可计算的)函数都可以递归实现,也可以使用while循环(或条件跳转,基本上是同一回事)来实现。如果您确实将自己限制为for loops,则将仅获得这些函数的子集(如果基本操作合理,则为原始递归函数)。当然,这是一个很大的子集,恰好包含您可能在实践中遇到的每个功能。

更重要的是,很多功能非常容易递归实现,而很难以迭代方式实现(手动管理调用堆栈不计算在内)。


1

是的,通过Thanakron Tandavas

当您解决可以通过分治法解决的问题时,递归非常好。

例如:河内塔

  1. N环尺寸不断增加
  2. 3极
  3. 环开始堆叠在杆1上。目标是移动环,使它们堆叠在杆3上...但是
    • 一次只能移动一枚戒指。
    • 不能将较大的戒指放在较小的戒指上。
  4. 迭代解决方案“功能强大而丑陋”;递归解决方案是“优雅的”。

一个有趣的例子。我想您知道MC Er的论文“河内的塔和二进制数字”。在3brown1blue的精彩视频中也进行了处理。
Andrestand

0

我似乎记得我的计算机科学教授曾经说过,所有具有递归解决方案的问题也都具有迭代解决方案。他说,递归解决方案通常较慢,但与迭代解决方案相比,它们更易于推理和编码时,经常使用它们。

但是,对于更高级的递归解决方案,我不认为它总是能够使用简单的for循环来实现它们。


总是有可能将递归算法转换为迭代算法(使用堆栈)。您可能不会以一个特别简单的循环结束,但是有可能。
Bernhard Barker


-6

简短的答案:折衷是递归更快,并且在几乎所有情况下for循环占用更少的内存。但是,通常有一些方法可以更改for循环或递归以使其运行更快

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.