如果我运行以下代码:
public static void main(String[] argsv) {
long whichFib = 45;
long st;
st = System.currentTimeMillis();
System.out.println(recursiveFib(whichFib));
System.out.println("Recursive version took " + (System.currentTimeMillis() - st) + " milliseconds.");
st = System.currentTimeMillis();
System.out.println(iterativeFib(whichFib));
System.out.println("Iterative version took " + (System.currentTimeMillis() - st) + " milliseconds.");
}
public static long recursiveFib(long n) {
if (n == 0)
return 0;
if (n == 1 || n == 2)
return 1;
return recFib(n - 1) + recFib(n - 2);
}
public static long iterativeFib(long n) {
if (n == 0)
return 0;
else if (n == 1 || n == 2)
return 1;
long sum = 1;
long p = 1;
long u = 1;
for (int i = 2; i < n; i++) {
sum = p + u;
p = u;
u = sum;
}
return sum;
}
我得到以下输出:
1134903170 递归版本耗时5803毫秒。 1134903170 迭代版本需要0毫秒。
我觉得我在这里做错了。我认为尾部调用(递归fibonacci方法中的最后一行)将由编译器优化,使其更接近迭代版本。有没有人有任何想法为什么这么慢?它只是一个写得不好的函数吗?
N.B。我正在使用Oracle JDK 1.7
答案 0 :(得分:6)
return recFib(n - 1) + recFib(n - 2);
由于您正在进行两次递归调用,而不是一次,因此编译器不太可能进行传统的尾调用优化。
您可以查看this page有关如何使用尾调用优化编写递归Fibonacci求解器的想法。
答案 1 :(得分:5)
正如其他答案所指出的那样,你的函数不是尾递归的,这里是斐波那契的尾递归版本:
long fibonacci(int n) {
if (n == 0)
return 0;
else
return fibonacciTail(n, 1, 0, 1);
}
long fibonacciTail(int n, int m, long fibPrev, long fibCurrent) {
if (n == m)
return fibCurrent;
else
return fibonacciTail(n, m + 1, fibCurrent, fibPrev + fibCurrent);
}
此外,JVM不进行尾调用优化,因此将为每个递归调用分配堆栈帧,这使得这非常昂贵。但是,请务必注意这在技术上依赖于实现,请参阅有关执行TCO的IBM SDK链接的注释,以及this所以请提供更多信息。
优化版本将手动进行尾调用优化,将上述内容转换为带有可变重新分配的while循环:
long fibonacciIter(int n) {
int m = 1;
long fibPrev = 0;
long fibCurrent = 1;
while (n != m) {
m = m + 1;
int current = fibCurrent;
fibCurrent = fibPrev + fibCurrent;
fibPrev = current;
}
return fibCurrent;
}
答案 2 :(得分:1)
在递归版本中,您正在递归地创建函数,这很昂贵,因为函数调用涉及将变量推入堆栈,堆栈管理等,而迭代操作在同一堆栈上。
答案 3 :(得分:1)
在递归代码中,调用次数与答案成正比,即它是O(exp(n))
在迭代方法中,运行时间与循环次数成比例。 O(n)
更糟糕的是,循环操作比递归调用快得多,因此即使相同迭代的顺序仍然会明显加快。
你可以像这样编写迭代的fib。
public static long iterativeFib(int n) { // no chance of taking a long
long a = 0, b = 1;
while(n-- > 0) {
long c = a + b;
a = b;
b = c;
}
return c;
}
有没有人知道为什么这么慢?它只是一个写得不好的函数吗?
Java不是一种函数式语言,它不进行尾调用优化。这意味着迭代通常比Java中的递归快得多。 (也有例外)