用java实现奇异递归优化

用java实现奇异递归优化,java,recursion,stack-overflow,tail-recursion,Java,Recursion,Stack Overflow,Tail Recursion,当我试图回答这个问题时,我遇到了一些奇怪的结果: 但是你不需要读那篇文章。我将在这里给出相关的上下文。 这可能看起来很长,但如果你通读一遍,其实并没有那么复杂。我希望大家都会感兴趣。就上下文而言 syra(n) = { 1 if n=1; n + syra(n/2) if n is even; and n + syra(3n+1) if n is odd } 及 e、 g,, syralen(1)=1,syralen(2)

当我试图回答这个问题时,我遇到了一些奇怪的结果:

但是你不需要读那篇文章。我将在这里给出相关的上下文。 这可能看起来很长,但如果你通读一遍,其实并没有那么复杂。我希望大家都会感兴趣。就上下文而言

syra(n) = { 1 if n=1; 
            n + syra(n/2) if n is even; and 
            n + syra(3n+1) if n is odd
          }

e、 g,,
syralen(1)=1
syralen(2)=2,因为我们需要执行两个步骤。
syra(10)=10+syra(5)=15+syra(16)=31+syra(8)=39+syra(4)=43+syra(2)=45+syra(1)=46
。因此,syra(10)需要7个步骤。因此,syralen(10)=7

最后

lengths(n) = syralen(1)+syralen(2)+...+syralen(n)
那里的问题海报试图计算长度(n)

我的问题是关于Op发布的递归解决方案(这是该问题的第二个片段)。我会把它转寄到这里:

public class SyraLengths{

        int total=1;
        public int syraLength(long n) {
            if (n < 1)
                throw new IllegalArgumentException();
            if (n == 1) {
                int temp=total;
                total=1;
                return temp;
            }
            else if (n % 2 == 0) {
                total++;
                return syraLength(n / 2);
            }
            else {
                total++;
                return syraLength(n * 3 + 1);
            }
        }

        public int lengths(int n){
            if(n<1){
                throw new IllegalArgumentException();
            }
            int total=0;
            for(int i=1;i<=n;i++){
                total+=syraLength(i);
            }

            return total;
        }

        public static void main(String[] args){
            System.out.println(new SyraLengths().lengths(5000000));
        }
       }
对于
测试值=50000
,输出为:

5075114
SyraLengths time taken: 44
5075114
SyraSlow time taken: 31
62634795
SyraLengths time taken: 378
Exception in thread "main" java.lang.StackOverflowError
    at SyraSlow.syraLen(SyraSlow.java:15)
    at SyraSlow.syraLen(SyraSlow.java:15)
    at SyraSlow.syraLen(SyraSlow.java:15)
正如预期的那样(我猜),普通递归稍微好一点。但当我进一步使用
TEST\u VAL=500000
时,输出是:

5075114
SyraLengths time taken: 44
5075114
SyraSlow time taken: 31
62634795
SyraLengths time taken: 378
Exception in thread "main" java.lang.StackOverflowError
    at SyraSlow.syraLen(SyraSlow.java:15)
    at SyraSlow.syraLen(SyraSlow.java:15)
    at SyraSlow.syraLen(SyraSlow.java:15)
为什么?Java在这里做了什么样的优化,SyraLengths版本不会出现堆栈溢出(它甚至在
TEST\u VAL=5000000
时也能工作)我甚至尝试使用基于累加器的递归版本,以防我的JVM进行一些尾部调用优化:

private long syraLenAcc(int i, long acc) {
        if (i == 1) return acc;
    if(i%2==0) {
        return syraLenAcc(i/2,acc+1);
    }
    return syraLenAcc(i * 3 + 1, acc+1);
    }
但我仍然得到了相同的结果(因此这里没有尾部调用优化)。那么,这里发生了什么


附言:如果你能想出更好的标题,请编辑成更好的标题。

对于原始版本,尾部递归优化是可能的(在JIT中)。但不知道它是否真的发生了。但有可能原始版本在使用堆(呃,我指的是堆栈)时效率稍高一些。(或者可能有一个功能上的差异,在粗略的检查中并不明显。)

好吧,结果有一个简单的解释:


我使用
长syraLen(int n)
作为方法签名。但是
n
的值实际上可以比
Integer.MAX\u值大得多。因此,
syraLen
得到了负输入,这就是问题所在。如果我将其更改为
long syraLen(long n)
,一切都会完美运行!如果(n<1)抛出新的IllegalArgumentException(),我希望我也把
放进去与原始海报相似。会为我节省一些时间。

当然可以。如果函数的最后一条语句是对自身的调用,则尾部递归可防止大量使用堆栈。在这种情况下,粗略地说,呼叫可以由跳转代替。函数基本上被包装成一种循环,将递归转化为迭代。(不要对我大喊大叫,我知道这有点过于简化。)但我已经尝试了第二个版本,使用累加器,如果尾部调用优化发生了,它应该会有所帮助。根据这个:尾部优化还没有出现。查看
尾部调用和尾部递归
。请看这里:
语句可能会混淆优化器的检查结果,并导致它放弃尾部递归优化。@Xeon..正是。。这就是我无法理解的原因。
private long syraLenAcc(int i, long acc) {
        if (i == 1) return acc;
    if(i%2==0) {
        return syraLenAcc(i/2,acc+1);
    }
    return syraLenAcc(i * 3 + 1, acc+1);
    }