Java “线程中的异常”;“主要”;栈溢出

Java “线程中的异常”;“主要”;栈溢出,java,recursion,hashmap,stack-overflow,Java,Recursion,Hashmap,Stack Overflow,我有一段代码,但我不明白为什么它会在线程“main”java.lang.StackOverflowerr中给我异常 问题是: Given a positive integer n, prints out the sum of the lengths of the Syracuse sequence starting in the range of 1 to n inclusive. So, for example, the call: lengths(3) will return the th

我有一段代码,但我不明白为什么它会在线程“main”java.lang.StackOverflowerr中给我异常

问题是:

Given a positive integer n, prints out the sum of the lengths of the Syracuse 
sequence starting in the range of 1 to n inclusive. So, for example, the call:
lengths(3)
will return the the combined length of the sequences:
1
2 1
3 10 5 16 8 4 2 1 
which is the value: 11. lengths must throw an IllegalArgumentException if 
its input value is less than one.
我的代码:

import java.util.HashMap;

public class Test {

HashMap<Integer,Integer> syraSumHashTable = new HashMap<Integer,Integer>();

public Test(){

}

public int lengths(int n)throws IllegalArgumentException{

    int sum =0;

    if(n < 1){
        throw new IllegalArgumentException("Error!! Invalid Input!");
    }   

    else{


        for(int i =1; i<=n;i++){

            if(syraSumHashTable.get(i)==null)
            {
                syraSumHashTable.put(i, printSyra(i,1));
                sum += (Integer)syraSumHashTable.get(i);

            }

            else{

                sum += (Integer)syraSumHashTable.get(i);
            }



        }

        return sum;

    }



}

private int printSyra(int num, int count){

    int n = num;

    if(n == 1){

        return count;
    }

    else{   
            if(n%2==0){

                return printSyra(n/2, ++count);
            }

            else{

                return printSyra((n*3)+1, ++count) ;

            }

    }


}
}
。 我知道问题在于递归。如果输入值很小,则不会发生错误,例如:5。但是当这个数字很大时,比如90090249,我在线程“main”java.lang.StackOverflowerr中得到了一个异常。谢谢大家的帮助。:)

我差点忘了错误消息:

Exception in thread "main" java.lang.StackOverflowError
at Test.printSyra(Test.java:60)
at Test.printSyra(Test.java:65)
at Test.printSyra(Test.java:60)
at Test.printSyra(Test.java:65)
at Test.printSyra(Test.java:60)
at Test.printSyra(Test.java:60)
at Test.printSyra(Test.java:60)
at Test.printSyra(Test.java:60)

这是递归算法的固有问题。如果递归的数量足够大,就无法真正避免堆栈溢出,除非该语言能够保证尾部调用优化(Java和大多数类似C的语言不能)。真正修复它的唯一方法是“展开”递归,迭代地重写算法或使用辅助函数来模拟递归调用的状态传递,而不实际嵌套调用。

您的算法很好。但是,
int
对于您的计算来说太小了,此输入失败:

printSyra(113383, 1);
在某个点上,整数溢出为负值,您的实现会变得疯狂,无限递归。将
int-num
更改为
long-num
,一段时间内你会没事的。稍后您将需要
biginger

请注意,根据Wikipedia on(粗体):

对于任何小于1亿的初始起始数,最长的级数为63728127,其步数949步。小于10亿的起始数字为670617279,步数为986步;小于100亿的起始数字为9780657630,步数为1132步

步骤总数相当于您所期望的最大嵌套级别(堆栈深度)。因此,即使是相对较大的数字,也不应出现堆栈溢出错误。请使用
biginger
查看此实现:

private static int printSyra(BigInteger num, int count) {
    if (num.equals(BigInteger.ONE)) {
        return count;
    }
    if (num.mod(BigInteger.valueOf(2)).equals(BigInteger.ZERO)) {
        return printSyra(num.divide(BigInteger.valueOf(2)), count + 1);
    } else {
        return printSyra(num.multiply(BigInteger.valueOf(3)).add(BigInteger.ONE), count + 1);
    }
}
它甚至适用于非常大的价值:

printSyra(new BigInteger("9780657630"), 0)  //1132
printSyra(new BigInteger("104899295810901231"), 0)  //2254

一种解决方案是允许JVM使用java-Xss参数为堆栈递归占用更多空间。它的默认值小于1兆字节,IIRC,最多可以限制为几百个递归

更好的解决方案是重写练习而不使用递归:

private int printSyra(int num){
    int count = 1;    
    int n = num;    
    while(n != 1){

            if(n%2==0){    
                n = n/2;
                ++count;
            }    
            else{    
                n=(n*3)+1;
                ++count;    
            }    
    }
    return count;
}

很可能是整数溢出。用long代替。(这里不需要使用递归,尽管如果实现正确,对于某些数字范围应该不会出现堆栈溢出错误)。我在这里没有看到递归??你在隐瞒什么吗??你的
printSyra(i,1)
方法在哪里?@RohitJain
printSyra
调用
printSyra
@Pshemo。。你怎么能这么想??它不在那里。@RohitJain检查
printSyra
方法的返回。不确定为什么要进行向下投票。。我错过什么了吗?如果你投反对票,请发表评论。递归在这里很好。真正的问题是整数溢出。嗯,你确定吗由于应用程序递归太深而发生堆栈溢出时引发。我确定,因为我知道OP在
printSyra
中正在做什么。应该有足够的堆栈用于几百个级别。@Ray.R.Chua最好的解决方案是在不使用递归的情况下实现它(这样可以避免StackOverflow错误)。另外,由于您无法知道在计算3x+1时将生成的最大数,因此请避免使用整数,并使用更大的
long
或可能是BigInteger。我想我自己也很困惑,在这里-我认为堆栈溢出通常发生在相对较小的堆栈(存储函数状态的地方)时递归调用太多后内存不足,不是因为某个整数太大。除非我误解了你的答案?我同意使用int而不是long可能是OP问题的真正原因,但仍然有可能递归到足够深,从而导致堆栈溢出,而不管输入数字有多大。@Tomasz:谢谢。我已经将int num更改为long num,现在我面临另一个错误:java.util.HashMap.resize(HashMap.java:462)处java.util.HashMap.addEntry(HashMap.java:755)处java.util.HashMap.put(HashMap.java:385)处Test.length(Test.java:26)处TestApp.main(TestApp.java:10)处的线程“main”java.OutOfMemoryError中的异常我对这个答案的主要问题是:技术上正确,但有误导性。更好的解决方案是完全避免递归(参见@greyfairer的答案)。事实上,在大多数情况下(在Java中),你应该避免递归,但没有很好的理由。@Kiyura:我写道:“[…]整数溢出[…]并且你的实现变得疯狂,无限递归”-这个实现失败的原因不是因为递归太深(正如维基百科所说,它从来没有那么深),但由于整数溢出,这会导致异常深的递归。当实现正确时,
StackOverflowerError
不是问题。但是你是对的,如果没有尾部递归优化,这个实现仍然有点低效——但从可读性的角度来看,这一点非常清楚。@Ray.R.Chua:在接近尾声时,你的HashMap将存储大约1亿个元素。你需要它吗?至于加速,如果您遇到在使用HashMap之前计算过的元素,您可以在printSyra中尽早返回值。我的递归实现可以在长度为2254()的N=104899295810901231下运行。虽然我同意递归可能是一个问题,但在递归问题出现之前,中间数可能会溢出整数的范围。您的版本仍然无法计算小到
113383
-的数,但不是抛出
StackOverflowException
,而是进入无限循环。此外,增加堆栈大小不会有助于断开的算法(错误的变量类型)。
private int printSyra(int num){
    int count = 1;    
    int n = num;    
    while(n != 1){

            if(n%2==0){    
                n = n/2;
                ++count;
            }    
            else{    
                n=(n*3)+1;
                ++count;    
            }    
    }
    return count;
}