【问题标题】:Exception in thread "main" java.lang.StackOverflowError线程“主”java.lang.StackOverflowError 中的异常
【发布时间】:2012-09-28 00:23:56
【问题描述】:

我有一段代码,但我不知道为什么它在线程“main”java.lang.StackOverflowError 中给了我异常。

这是问题:

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) ;

            }

    }


}
}

驱动代码:

public static void main(String[] args) {
    // TODO Auto-generated method stub
    Test s1 = new Test();
    System.out.println(s1.lengths(90090249));
    //System.out.println(s1.lengths(5));
}

。 我知道问题在于递归。如果输入的值很小,则不会发生错误,例如:5。但是当数字很大时,例如 90090249,我在线程“main”java.lang.StackOverflowError 中出现异常。感谢你的帮助。 :)

我差点忘了错误信息:

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)

【问题讨论】:

  • 很可能是整数溢出。改用 long 。 (并且这里不需要使用递归,虽然如果实现正确,在一定的数字范围内应该不会出现 StackOverflow 错误)。
  • 我在这里实际上没有看到递归??你有什么隐瞒吗??你的printSyra(i,1) 方法在哪里?
  • @RohitJain printSyra 调用 printSyra
  • @Pshemo.. 你怎么能假设?它不存在..
  • @RohitJain 检查printSyra 方法的返回值。

标签: java recursion hashmap stack-overflow


【解决方案1】:

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

printSyra(113383, 1);

在某些时候整数溢出到负值并且你的实现变得疯狂,无限递归。将int num 更改为long num,你会没事的 - 一段时间。稍后您将需要BigInteger

请注意,根据维基百科Collatz conjecture(我的粗体字):

任何小于 1 亿的初始起始数字的最长级数是 63,728,127,有 949 步。对于小于 10 亿的起始数字,它是 670,617,279,有 986 个步骤,对于小于 100 亿的数字,它是 9,780,657,630,有 1132 个步骤

总步数等于您可以预期的最大嵌套级别(堆栈深度)。因此,即使是相对较大的数字 StackOverflowError 也不应该出现。使用BigInteger 看看这个实现:

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

【讨论】:

  • 我想我自己很困惑,在这里我认为堆栈溢出通常发生在相对较小的堆栈(存储函数状态的地方)在太多递归调用后耗尽内存时,不是因为某个整数太大。除非我误解了你的答案?我确实同意使用 int 而不是 long 可能是 OP 问题的真正原因,但是无论您可以使输入数字多大,它仍然有可能递归到足以导致堆栈溢出。
  • @Tomasz:谢谢。我已将 int num 更改为 long num,现在我面临另一个错误:线程“主”java.lang.OutOfMemoryError 中的异常:java.util 的 java.util.HashMap.resize(HashMap.java:462) 处的 Java 堆空间。 HashMap.addEntry(HashMap.java:755) 在 java.util.HashMap.put(HashMap.java:385) 在 Test.lengths(Test.java:26) 在 TestApp.main(TestApp.java:10)
  • 我对这个答案的主要问题:技术上正确,但具有误导性。更好的解决方案是完全避免递归(参见@greyfairer 的回答)。事实上,在大多数情况下(在 Java 中)你应该避免递归,没有很好的理由。
  • @Kiyura:我写道:“[...]整数溢出 [...] 并且你的实现变得疯狂,无限递归” - 这个实现失败的原因是不是因为递归太深(正如维基百科所说,它从来没有那么深),而是因为整数溢出 - 这会导致异常深度递归。当实现正确时,StackOverflowError 不是问题。但是你是对的,如果没有尾递归优化,这个实现仍然有点低效 - 但从可读性的角度来看非常清楚。
  • @Ray.R.Chua:接近尾声,您的 HashMap 将存储大约 100 百万个元素。你甚至需要它吗?至于加速,如果遇到使用HashMap之前计算过的元素,可以在printSyra中提前返回值。
【解决方案2】:

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

【讨论】:

  • 不知道为什么要投反对票..我错过了什么吗?如果您投反对票,请发表评论。
  • 递归在这里很好。真正的问题是整数溢出。
  • 嗯,你确定吗? Documentation for java.lang.StackOverflowError:由于应用程序递归太深而发生堆栈溢出时抛出。
  • 我确定,因为我知道 OP 在printSyra 中正在做什么。应该有足够的堆栈用于几百个级别。
  • @Ray.R.Chua 最好的解决方案是在没有递归的情况下实现它(这样你就可以摆脱 StackOverflow 错误)。此外,由于您不知道在计算 3x+1 时将生成的最大数,因此请避免使用整数并使用更大的 long 或 BigInteger。
【解决方案3】:

一种解决方案是允许 JVM 为堆栈递归占用更多空间,使用 java -Xss 参数。它的默认值小于一兆字节,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;
}

【讨论】:

  • 我的递归实现可以在长度为 2254 (ericr.nl/wondrous/delrecs.html) 的 N = 104899295810901231 下运行。虽然我同意递归可能是一个问题,但在递归问题出现之前,中间数可能会超出整数范围。
  • 您的版本仍然无法计算像 113383 这样小的数字 - 但不是抛出 StackOverflowException,而是进入无限循环。此外,增加堆栈大小无助于破坏算法(错误的变量类型)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-10-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-05-18
  • 1970-01-01
相关资源
最近更新 更多