递归与迭代

时间:2013-03-28 17:11:15

标签: algorithm recursion iteration

说使用recursion到处都可以使用for循环是否正确?如果递归通常较慢,那么将其用于循环迭代的技术原因是什么?

如果始终可以将递归转换为for循环,那么可以采用经验法则吗?

11 个答案:

答案 0 :(得分:124)

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

某些优化(如tail call optimization)会使递归更快,但并非总是可行,并且未在所有语言中实现。

使用递归的主要原因是

  • 在许多情况下,当它模仿我们解决问题的方法时,它更直观
  • 使用递归更容易探索像树一样的数据结构(或者在任何情况下都需要堆栈)

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

答案 1 :(得分:47)

  

是否可以使用for循环来处理递归?

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

  

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

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

  

如果始终可以将递归转换为for循环,那么可以采用经验法则吗?

为迭代解释时最佳理解的算法编写迭代程序;为递归程序编写最佳递归程序。

例如,通常以递归方式解释搜索二叉树,运行快速排序以及在许多编程语言中解析表达式。这些也是最好的递归编码。另一方面,计算因子和计算斐波纳契数在迭代方面更容易解释。使用它们的递归就像用大锤拍苍蝇:这不是一个好主意,即使大锤在它上面做得非常好 +

<小时/> + 我从Dijkstra的“编程学科”中借用了大锤的类比。

答案 2 :(得分:23)

问题:

  

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

答案:

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

尝试的另一个好处是:尝试迭代编写Merge排序。这需要你一段时间。

问题:

  

是否可以使用for循环来处理递归?

答案:

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

问题:

  

如果始终可以将递归转换为for循环,那么可以采用经验法则吗?

答案:

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

提示:当您解决可以通过divide and conquer技术解决的问题时递归很好。

答案 3 :(得分:4)

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

答案 4 :(得分:3)

要使用迭代编写等效方法,我们必须显式使用堆栈。迭代版本需要堆栈用于其解决方案这一事实表明问题很难以使其受益于递归。作为一般规则,递归最适合于使用固定数量的内存无法解决的问题,因此在迭代求解时需要堆栈。 话虽如此,递归和迭代可以显示相同的结果,同时它们遵循不同的模式。要确定哪种方法更好的是逐个案例,最佳实践是根据问题所遵循的模式进行选择。

例如,找到第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)); 
   }
}

答案 5 :(得分:1)

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

所有(可计算的)函数都可以递归地实现并使用while循环(或条件跳转,这基本上是相同的)。如果你真的把自己限制在for loops,那么你只会获得这些函数的一个子集(原始递归函数,如果你的基本操作是合理的)。当然,它是一个非常大的子集,碰巧包含了你可能在实践中加入的每一个函数。

更重要的是,许多函数很容易递归实现,并且难以迭代实现(手动管理调用堆栈不计算在内)。

答案 6 :(得分:0)

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

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

答案 7 :(得分:0)

是的,saidThanakron Tandavas

  

当您解决可以通过分而治之的技术解决的问题时,递归是很好的。

例如:河内的塔楼

  1. N圈越来越大
  2. 3极
  3. 戒指开始堆放在杆子1上。目标是移动戒指 他们被堆放在杆3上......但是
    • 一次只能移动一个戒指。
    • 不能在较小的上面放置较大的戒指。
  4. 迭代解决方案是“强大而丑陋的”;递归解决方案是“优雅的”。

答案 8 :(得分:-1)

已经给出了很多充分的理由,我想多分享一点。有些问题可以通过递归很好地解决,而只能通过递归来解决。一个示例示例为:

打印数组的所有值,但数组的项也可以是数组。也可以使用嵌套数组,但是我们不确定嵌套数组的深度。

一个漂亮的解决方案是:

<?php
  function print_all_items($array){

       foreach($array as $key=>$value){
           if(is_array($value)) { 
             print_all_items($value);
           }
           else{
             echo "$key => $value <br/>";
           }
       }
  }

$array = array("first" => "first value",  
              "second" => "second value",  
              "third" => "third value",  
               "fourth"=>array(1,2,3,4,5),
              "fifth"=>array(
                       "second array 1",
                       "second array 2",
                       "second array 3",
                       array(
                           "third array 1",
                           "third array 2",
                           "third array 3",
                           )
                       ),
              );

print_all_items($array);

输出:

first => first value 
second => second value 
third => third value 
0 => 1 
1 => 2 
2 => 3 
3 => 4 
4 => 5 
0 => second array 1 
1 => second array 2 
2 => second array 3 
0 => third array 1 
1 => third array 2 
2 => third array 3 

由于问题的性质需要动态解决,因此iterative approach无法解决这类问题。

答案 9 :(得分:-3)

与纯迭代方法相比,递归+记忆可以导致更有效的解决方案,例如,检查一下: http://jsperf.com/fibonacci-memoized-vs-iterative-for-large-n

答案 10 :(得分:-5)

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