web-dev-qa-db-ja.com

スレッド「メイン」Java.lang.StackOverflowErrorの例外

コードの一部があり、スレッド「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)
5
Ray.R.Chua

あなたのアルゴリズムは大丈夫です。ただし、intは計算には小さすぎるため、次の入力では失敗します。

printSyra(113383, 1);

ある時点で整数が負の値にオーバーフローし、実装が狂って無限に繰り返されます。変化する int numからlong numそしてあなたは大丈夫でしょう-しばらくの間。後でBigIntegerが必要になります。

コラッツの予想 (太字の鉱山)に関するウィキペディアによると、次のことに注意してください。

1億未満の初期開始数の最長進行は、63,728,127であり、949ステップがあります。 10億未満の開始数の場合は、986ステップの670,617,279であり、100億未満の数の場合は、1132ステップの9,780,657,630です。

ステップの総数は、期待できる最大のネストレベル(スタックの深さ)に相当します。したがって、比較的大きな数であっても、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
8

これは、再帰的アルゴリズムに固有の問題です。再帰の数を十分に大きくすると、言語が末尾呼び出しの最適化を保証できない限り、スタックオーバーフローを実際に回避することはできません(JavaおよびほとんどのCのような言語は保証しません)。これを本当に修正する唯一の方法は、再帰を「展開」し、アルゴリズムを繰り返し書き換えるか、ヘルパー関数を使用して、実際に呼び出しをネストせずに再帰呼び出しの状態受け渡しをシミュレートすることです。

1
jrajav

1つの解決策は、Java -Xssパラメーターを使用して、JVMがスタック再帰のためにより多くのスペースを使用できるようにすることです。デフォルトはメガバイト未満の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;
}
1
GreyFairer