递归方法总是比Java中的迭代方法更好吗?
它们也可以用来代替迭代,反之亦然吗?
答案 0 :(得分:24)
递归方法总是比java中的迭代方法更好吗?
否强>
它们也可以用来代替迭代,反之亦然吗?
您可以始终使递归函数成为迭代函数。 (如果内存可以处理它,请参阅有趣的链接here)
对于某些情况,最好使用递归(比如在处理树时......在二叉树上旅行......等等)。对我来说,如果使用循环并不比递归更复杂和困难,我更喜欢使用循环。
递归使用更多内存,但有时更清晰,更易读。 使用循环可以提高性能,但程序员(以及他的表现)的递归有时会更好。
因此,总而言之,决定使用什么 - 递归或迭代,取决于你想要实现什么,以及对你来说更重要(可读性,性能......)以及要求递归或迭代就像要求优雅或表现。
考虑 factorial 的这两个实现:
<强>迭代:强>
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更大时差异会更大..答案 1 :(得分:3)
更正其他答案:任何迭代都可以转换为递归(警告可能会发生堆栈溢出)。 然而,并非所有递归都可以直接转换为迭代。通常,您需要某种形式的临时空间来保存否则将存储在堆栈中的数据。
至于选择哪一个:这取决于您的语言和编写递归解决方案的便利性。
编辑,以“直接”澄清我的意思:
有三种类型的递归,基于它们如何直接转换为迭代:
fact(N)
的阶乘是典型的例子:在递归公式中,最后一个操作不是递归调用,而是乘法。这些类型的递归调用可以很容易地转换为尾部调用优化形式,然后转换为迭代形式(将factorial转换为尾部调用可优化形式,必须使用双参数版本fact(n, acc)
,其中acc
是累计结果。)答案 2 :(得分:2)
递归对于程序员理解程序是有好处的,但很多时候它们会导致堆栈溢出因此总是更喜欢迭代它们。
实际上,递归很少是最常见的解决问题的有效方法,迭代几乎总是更有效。这是因为由于调用堆栈在递归期间被大量使用,因此通常会产生与递归调用相关的更多开销。
这意味着许多计算机编程语言将花费更多时间来维护调用堆栈,然后他们将实际执行必要的计算
递归使用的内存多于迭代吗?
一般来说,是的。这是因为调用堆栈的广泛使用。
我应该使用递归还是迭代?
通常使用递归是因为它实现起来更简单,并且它通常比迭代解决方案更“优雅”。请记住,在递归中完成的任何操作也可以迭代完成,但是通过递归通常存在性能缺陷。但是,根据您尝试解决的问题,性能缺陷可能非常微不足道 - 在这种情况下,使用递归是有意义的。通过递归,您还可以获得额外的好处,即其他程序员可以更轻松地理解您的代码 - 这总是一件好事。
答案 3 :(得分:0)
你的意思是什么?每次递归也可以用迭代实现,反之亦然。有时递归更容易理解。但是由于recurssion使堆栈膨胀,许多嵌套调用可能会导致内存不足异常。在这种情况下,recursion显然不是你的最佳选择。
答案 4 :(得分:0)
声明“递归总是好于迭代”是错误的。有些情况比任何一个都优于另一个。
很难给出关于使用哪种算法类型的决定性答案,因为它取决于具体情况。例如,Fibonacci序列的常见教科书递归情况使用递归是非常低效的,因此在这种情况下最好使用迭代。相反,使用递归可以更有效地实现遍历树。
要回答第二个问题,是的,任何迭代算法都可以使用递归实现,反之亦然。
答案 5 :(得分:0)
通常递归方法需要几行代码,但需要深入思考算法。如果你犯了一个逻辑错误,你可能会获得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);
}
总是很好地反思每个提案的不同想法,总是考虑代码行,复杂性,清晰度,凝聚力等。
答案 6 :(得分:0)
严格来说,递归和迭代都同样强大。任何递归解决方案都可以实现为具有堆栈的迭代解决方案。逆变换可能比较棘手,但最重要的只是将状态传递给调用链。
在Java中,有一种情况是递归解决方案优于(幼稚)迭代解决方案,以及一种基本等效的情况。在大多数其他情况下,迭代解决方案将是优越的,因为避免了函数调用开销。
如果函数隐式使用堆栈,那么递归解决方案通常会更优越。考虑深度优先搜索:
void walkTree(TreeNode t) {
doSomething(t);
if (t.hasLeft()) { walkTree(t.getLeft()); }
if (t.hasRight()) { walkTree(t.getRight()); }
}
与等效迭代解决方案相比
void walkTree(TreeNode t) {
Stack<TreeNode> s = new Stack<TreeNode>();
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()); }
}
自&#34; walkList&#34;在函数中完成的最后一件事,JIT将它基本上转换为&#34; n = n.getNext();转到开始&#34;,这将使它等同于迭代解决方案。
在大多数其他情况下,迭代解决方案将更胜一筹。例如,如果您想进行广度优先搜索,那么您可以在迭代解决方案中使用Queue
而不是Stack
,并使其立即工作,同时将其转换为递归解决方案要求您支付调用堆栈中的隐式堆栈,并且仍然为队列付费。
答案 7 :(得分:0)
如果我们记住java的值传递机制,我们可以理解递归会消耗更多的内存,因为每次调用都会传递对象引用地址的副本或某种原始类型的值。因此,作为每个递归调用传递的马赫参数作为马赫内存,您将为每个调用使用,另一方面,循环很轻松。 但是我们当然知道存在“分而治之”的算法,例如Mergesort或树上的迭代,它们在递归的帮助下消耗较少的步骤来给出结果。所以在这些情况下我认为最好使用递归。