递归方法总是比Java中的迭代方法更好吗?

递归方法总是比Java中的迭代方法更好吗?

它们也可以用来代替迭代,反之亦然吗?

递归方法总是比java中的迭代方法更好吗?

没有

它们也可以用来代替迭代,反之亦然吗?

总是可以将递归函数作为迭代函数。 (如果内存可以处理它,请在此处查看有趣的链接)

在某些情况下,最好使用递归(比如在处理树时……在二叉树上旅行……等等)。 对我来说,如果使用循环并不比递归更复杂和困难,我更喜欢使用循环。

递归使用更多内存,但有时更清晰,更易读。 使用循环可以提高性能,但程序员(以及他的表现)的递归有时会更好。

因此,总而言之,决定使用什么 – 递归或迭代,取决于你想要实现什么,以及对你来说更重要(可读性,性能……)以及询问递归或迭代就像要求优雅或性能


考虑这两个阶乘的实现:

迭代:

private int Factorial(int num) { int result = 1; if (num <= 1) return result; while (num > 1) { result * = num; num--; } return result; } 

递归:

 private int Factorial(int num) { if (num <= 1) return 1; return num * Factorial(num - 1); } 

哪种方法更具可读性?

显然是递归的,它是直接的,可以编写并从第一次尝试成功运行 - 它只是将数学定义转换为Java

哪种方法更有效?

num = 40为例,这里是时间比较

 long start = System.nanoTime(); int res = Factorial(40); long end = System.nanoTime(); long elapsed = end - start; System.out.println("Time: " + elapsed); 

2993为递归

2138为迭代

当然,当num更大时,差异会更大。

更正其他答案:任何迭代都可以转换为递归(警告可能会发生堆栈溢出)。 但是 ,并非所有递归都可以直接转换为迭代。 通常,您需要某种forms的临时空间来保存否则将存储在堆栈中的数据。

至于选择哪个:这取决于您的语言和编写递归解决方案的便利性。


编辑,以“直接”澄清我的意思:

有三种类型的递归,基于它们如何直接转换为迭代:

  • 尾调用可优化,其中方法中的最后一个操作是递归调用。 这些可以直接转换为循环。
  • 非尾部调用的递归可以优化,因为它需要在调用之间保留信息,但不需要堆栈。 表示为fact(N)阶乘是典型的例子:在递归公式中,最后一个操作不是递归调用,而是乘法。 这些类型的递归调用可以很容易地转换为尾部调用优化forms,然后转换为迭代forms(将阶乘变换为尾部调用可优化forms,必须使用双参数版本fact(n, acc) ,其中acc是累积结果)。
  • 需要在每次调用时保留状态的递归。 经典示例是预订树遍历:在每次调用时,您必须记住父节点。 没有办法将其直接转换为迭代。 相反,您必须构建自己的堆栈以保持每次调用的状态。

递归对于程序员理解程序是有好处的,但很多时候它们会导致堆栈溢出,因此总是更喜欢迭代它们

事实上,递归很少是解决问题的最有效方法,迭代几乎总是更有效。 这是因为由于调用堆栈在递归期间被大量使用,因此通常会产生与递归调用相关的更多开销。
这意味着许多计算机编程语言将花费更多时间来维护调用堆栈,然后它们将实际执行必要的计算。

递归是否比迭代使用更多内存? 一般来说,是的。 这是因为调用堆栈的广泛使用。

我应该使用递归或迭代吗?

通常使用递归是因为它实现起来更简单,并且它通常比迭代解决方案更“优雅”。 请记住,在递归中完成的任何操作也可以迭代完成,但是通过递归通常存在性能缺陷。 但是,根据您尝试解决的问题,性能缺陷可能非常微不足道 – 在这种情况下使用递归是有意义的。 通过递归,您还可以获得额外的好处,即其他程序员可以更轻松地理解您的代码 – 这总是一件好事。

声明“递归总是优于迭代”是错误的。 有些情况比任何一个都优于另一个。

很难给出关于使用哪种算法类型的决定性答案,因为它取决于具体情况。 例如,Fibonacci序列的常见教科书递归情况使用递归是非常低效的,因此在这种情况下最好使用迭代。 相反,使用递归可以更有效地实现遍历树。

要回答第二个问题,是的,任何迭代算法都可以使用递归实现,反之亦然。

严格来说,递归和迭代都同样强大。 任何递归解决方案都可以实现为具有堆栈的迭代解决方案。 逆变换可能比较棘手,但最重要的只是将状态传递给调用链。

在Java中,有一种情况是递归解决方案优于(幼稚)迭代解决方案,以及一种基本等效的情况。 在大多数其他情况下,迭代解决方案将是优越的,因为避免了函数调用开销。

如果函数隐式使用堆栈,那么递归解决方案通常会更优越。 考虑深度优先搜索:

 void walkTree(TreeNode t) { doSomething(t); if (t.hasLeft()) { walkTree(t.getLeft()); } if (t.hasRight()) { walkTree(t.getRight()); } } 

与等效的迭代解决方案相比

 void walkTree(TreeNode t) { Stack s = new Stack(); s.push(t); while (!s.empty()) { TreeNode t = s.pop(); doSomething(t); if (t.hasLeft()) { s.push(t.getLeft()); } if (t.hasRight()) { s.push(t.getRight()); } } } 

在迭代的情况下,您将不得不为Stack<>对象创建的任何垃圾付费。 在递归的情况下,您将使用进程堆栈,它不会创建任何垃圾或分配任何内存。 递归解决方案容易受到堆栈溢出的影响,但否则会运行得更快。

如果函数使用尾递归,那么这两个解决方案将是等价的,因为JIT会将递归的转换为迭代的转换。 尾递归是指函数执行的最后一件事是递归调用自身,允许编译器忽略任何累积状态。 例如,遍历列表

 void walkList(ListNode n) { doSomething(n); if (n.hasNext()) { walkList(n.getNext()); } } 

由于“walkList”是在函数中完成的最后一件事,因此JIT将它基本上转换为“n = n.getNext(); goto beginning”,这将使它等同于迭代解决方案。

在大多数其他情况下,迭代解决方案将更加优越。 例如,如果您想进行广度优先搜索,那么您可以在迭代解决方案中使用Queue而不是Stack ,并使其立即工作,同时将其转换为递归解决方案需要您同时支付隐含堆栈在调用堆栈中,仍然支付队列费用。

如果我们记住java的值传递机制,我们可以理解,递归会消耗更多内存,因为每次调用都会传递对象引用地址的副本或某些基本类型的值。 因此,作为每个递归调用传递的马赫参数作为马赫内存,您将为每个调用使用,另一方面,循环很轻。 但是我们当然知道存在“分而治之”算法,例如Mergesort或树上的迭代,它们在递归的帮助下消耗较少的步骤来给出结果。 所以在这些情况下我认为使用递归更好。

通常递归方法需要几行代码,但需要深入思考算法。 如果你犯了一个逻辑错误,你可能会得到一个StackOverflowError

以下是2个阶乘的编程示例:

Iteractive:

 public int calculateIteractiveFactorial(int n) { // Base case if (n == 1) { return 1; } int factorial = 1; for (int i = 1; i <= n; i++) { factorial = factorial * i; } return factorial; } 

递归:

 public int calculateRecursiveFactorial(int n) { // Base case if (n == 1) { return 1; } return n * calculateRecursiveFactorial(n - 1); } 

总是很好地反思每个提案的不同想法,总是考虑代码行,复杂性,清晰度,凝聚力等。

什么意思更好? 每次递归也可以用迭代实现,反之亦然。 有时递归更容易理解。 但是由于recurssion使堆栈膨胀,许多嵌套调用可能会导致内存不足exception。在这种情况下,recursion显然不是你的最佳选择。