回答你的问题非常简单:
通常情况下,采用传统方式,也就是使用return
手动展开堆栈。为什么?因为这并不像你想象的那样慢。除非递归计算非常琐碎且堆栈深度非常非常高,否则返回将不会明显影响算法的运行时间。
基本上,你有以下几个选择:
- 如果可能,将算法转换为迭代算法。
- 将算法转换为尾递归,并希望虚拟机能够重用堆栈帧。然后,从递归中返回实际上等同于一个简单的
return
。
- 抛出异常。然而,这比返回甚至更慢,因为必须构建堆栈跟踪,它最终也要遍历堆栈。还必须展开堆栈以检查
catch
。你什么也没赢得到。
前两个选项是可行的,但并非总是可行的。但老实说,请不要考虑这个问题。从深度堆栈返回并不是使您的算法变慢的原因。如果您有一个非常深的递归算法,则无论如何都存在问题(堆栈溢出,递归调用的成本),应该考虑重写您的算法。如果堆栈深度很低,则这不是问题。
这里有一个简单的Java测试程序,以向您展示我所说的内容:
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
public class DeepRecursion {
private long returnStartTime;
public int recurse(int i) {
int r = (int)Math.sqrt((double)i);
if(i == 0) {
returnStartTime = System.currentTimeMillis();
return r;
}
return r + recurse(i-1);
}
public void testRecursion(int i, PrintStream p) {
long startTime = System.currentTimeMillis();
int result = recurse(i);
long endTime = System.currentTimeMillis();
p.println(
"Recursion depth: " + i + " Result: " + result + "\n" +
"Time for recursion" + (returnStartTime - startTime) + "\n" +
"Time for return " + (endTime - returnStartTime) + "\n"
);
}
public void testIteration(int i, PrintStream p) {
long startTime = System.currentTimeMillis();
int result = 0;
for(int k = 0; k <= i; k++) {
int r = (int)Math.sqrt((double)k);
result += r;
}
long endTime = System.currentTimeMillis();
p.println("Iteration length: " + i + " Result: " + result + "\nTime: " + (endTime - startTime) );
}
public static void main(String[] args) {
DeepRecursion r = new DeepRecursion();
PrintStream nullStream = new PrintStream(new ByteArrayOutputStream());
for(int k = 0; k < 10; k++) {
for(int i = 10; i < 26; i++) {
r.testIteration(1 << i, k == 9 ? System.out : nullStream);
r.testRecursion(1 << i, k == 9 ? System.out : nullStream);
}
}
}
}
它计算一个递归函数,其堆栈深度将等于输入参数。该函数在每个堆栈帧中计算平方根以模拟一些非平凡的计算。它还以迭代方式计算相同的函数。为了热身JIT,在仅打印第十个结果之前,程序首先执行9次而不打印结果。这是我的结果(我必须使用-Xss1g
增加堆栈大小到1 GB)。这是来自我的机器的结果:
Iteration length: 1024 Result: 21360
Time for iteration: 0
Recursion depth: 1024 Result: 21360
Time for recursion 0
Time for return 0
Iteration length: 2048 Result: 60810
Time for iteration: 0
Recursion depth: 2048 Result: 60810
Time for recursion 0
Time for return 0
Iteration length: 4096 Result: 172768
Time for iteration: 0
Recursion depth: 4096 Result: 172768
Time for recursion 0
Time for return 0
Iteration length: 8192 Result: 490305
Time for iteration: 0
Recursion depth: 8192 Result: 490305
Time for recursion 0
Time for return 0
Iteration length: 16384 Result: 1390016
Time for iteration: 0
Recursion depth: 16384 Result: 1390016
Time for recursion 0
Time for return 0
Iteration length: 32768 Result: 3938198
Time for iteration: 0
Recursion depth: 32768 Result: 3938198
Time for recursion 0
Time for return 0
Iteration length: 65536 Result: 11152256
Time for iteration: 0
Recursion depth: 65536 Result: 11152256
Time for recursion 1
Time for return 0
Iteration length: 131072 Result: 31570201
Time for iteration: 0
Recursion depth: 131072 Result: 31570201
Time for recursion 1
Time for return 0
Iteration length: 262144 Result: 89347840
Time for iteration: 2
Recursion depth: 262144 Result: 89347840
Time for recursion 1
Time for return 1
Iteration length: 524288 Result: 252821886
Time for iteration: 2
Recursion depth: 524288 Result: 252821886
Time for recursion 4
Time for return 1
Iteration length: 1048576 Result: 715304448
Time for iteration: 5
Recursion depth: 1048576 Result: 715304448
Time for recursion 7
Time for return 3
Iteration length: 2097152 Result: 2023619820
Time for iteration: 9
Recursion depth: 2097152 Result: 2023619820
Time for recursion 14
Time for return 4
Iteration length: 4194304 Result: 1429560320
Time for iteration: 18
Recursion depth: 4194304 Result: 1429560320
Time for recursion 29
Time for return 12
Iteration length: 8388608 Result: -986724456
Time for iteration: 36
Recursion depth: 8388608 Result: -986724456
Time for recursion 56
Time for return 28
Iteration length: 16777216 Result: -1440040960
Time for iteration: 72
Recursion depth: 16777216 Result: -1440040960
Time for recursion 112
Time for return 61
Iteration length: 33554432 Result: 712898096
Time for iteration: 145
Recursion depth: 33554432 Result: 712898096
Time for recursion 223
Time for return 123
如您所见,从深度为一百万的栈返回需要3毫秒。更大的栈大小会导致更长的时间,可能是由于栈不再适合L3缓存。但是,如果你需要这么大的栈,你已经有了一个问题,就像上面所描述的那样。在Java中使用1GB的最大堆栈大小并不是最好的选择。任何小于131072的堆栈大小无法测量毫秒级别。在一个健康的算法中,堆栈应该比这个小得多,所以你应该总是没问题。
正如您所看到的,最快的解决方案是迭代的,因此,如果非常深的递归太慢,请完全避免它,而不仅仅是跳过返回。
结论
如果递归对您来说太慢了,请完全摒弃它。仅仅跳过返回不会有太大的差异。
throw new ResultFoundException(...);
尽管我不会冒险将其作为答案返回,以免被踩。 :) (注:该句代码中的异常类名 "ResultFoundException" 可能需要根据上下文进行调整翻译) - Joop Eggenbreak
会是return <value>;
而不是return recur(...);
。如果你正在遍历一棵树,通常会搜索下一个子节点,如果当前节点没有得到答案。也许你可以举出一个这种方法行不通的例子? - SylwesterMyClass v = myFunct(c.valueB); //做一些工作 if (v.valueE == 7) {
?(您可能需要添加一个参数来控制接下来的两个递归调用并不完全相同,而且需要避免从独立的深度/间接嵌套调用中逃脱时产生不良副作用。) - greybeard