私の問題は、再帰を使用すると通常Java.lang.StackOverflowErrorが発生することです。私の質問は-再帰がループよりもはるかに多くのスタックオーバーフローを引き起こすのはなぜですか、そして再帰を使用してスタックオーバーフローを回避する良い方法はありますか?
これは解決する試みです 問題107 、彼らの例ではうまくいきますが、それ自体の問題のためにスタック領域が不足します。
//-1 16 12 21 -1 -1 -1 16 -1 -1 17 20 -1 -1 12 -1 -1 28 -1 31 -1 21 17 28 -1 18 19 23 -1 20 -1 18 -1 -1 11 -1 -1 31 19 -1 -1 27 -1 -1 -1 23 11 27 -1
public class tries
{
public static int n=7,min=Integer.MAX_VALUE;
public static boolean[][] wasHere=new boolean[n][60000];
public static void main(String[] args)
{
int[] lines=new int[n]; Arrays.fill(lines, -1000); lines[0]=0;
int[][] networkMatrix=new int[n][n];
Scanner reader=new Scanner(System.in);
int sum=0;
for(int k=0; k<n; k++)
{
for(int r=0; r<n; r++)
{
networkMatrix[k][r]=reader.nextInt();
if(networkMatrix[k][r]!=-1) sum+=networkMatrix[k][r];
Arrays.fill(wasHere[k], false);
}
}
recursive(lines,networkMatrix,0,0);
System.out.println((sum/2)-min);
}
public static void recursive(int[] lines, int[][] networkMatrix, int row,int lastRow)
{
wasHere[row][value((int)use.sumArr(lines))]=true;
if(min<sum(lines)) return;
if(isAllNotMinus1000(lines)) min=sum(lines);
int[][] copyOfMatrix=new int[n][n];
int[] copyOfLines;
for(int i=0; i<n; i++)
{
copyOfLines=Arrays.copyOf(lines, lines.length);
for(int k=0; k<n; k++) copyOfMatrix[k]=Arrays.copyOf(networkMatrix[k], networkMatrix[k].length);
if(i!=0&©OfMatrix[i][row]!=0) copyOfLines[i]=copyOfMatrix[i][row];
copyOfMatrix[i][row]=0; copyOfMatrix[row][i]=0;
if(networkMatrix[row][i]==-1) continue;
if(wasHere[i][value((int)use.sumArr(copyOfLines))]) continue;
if(min<sum(copyOfLines)) continue;
recursive(copyOfLines,copyOfMatrix,i,row);
}
}
public static boolean isAllNotMinus1000(int[] lines)
{
for(int i=0; i<lines.length; i++) {if(lines[i]==-1000) return false;}
return true;
}
public static int value(int n)
{
if(n<0) return (60000+n);
return n;
}
public static int sum(int[] arr)
{
int sum=0;
for(int i=0; i<arr.length; i++)
{
if(arr[i]==-1000) continue;
sum+=arr[i];
}
return sum;
}
}
なぜ再帰はループよりもはるかに多くのスタックオーバーフローを引き起こすのですか
再帰呼び出しごとにスタック上のスペースが使用されるためです。再帰が深すぎる場合は、スタックの最大許容深度に応じて、StackOverflow
になります。
再帰を使用するときは、非常に注意して、必ず 基本ケース を指定する必要があります。再帰の基本ケースは、再帰が終了し、スタックがほどき始める条件です。これがStackOverflow
エラーを引き起こす再帰の主な理由です。基本ケースが見つからない場合、Stack
は有限のみであるため、無限再帰に入り、間違いなくエラーになります。
ほとんどの場合、スタックオーバーフローは、再帰メソッドが正しく定義されていないために発生し、存在しない、または到達できない終了条件でスタックメモリ空間が使い果たされます。正しく記述された再帰は、スタックオーバーフローを生成しません。
ただし、メソッドが正しく実装されていれば、メソッドがスタックオーバーフローevenを生成する場合があります。例えば:
結論:すべては特定のケースに依存し、スタックオーバーフローの原因を一般化することは不可能です。
各再帰呼び出しは、スタックの一部のスペースを使用します(引数、ローカル変数など、その1つの呼び出しに固有の何かを収容するため)。したがって、あまりにも多くの再帰呼び出しを行う場合(基本ケースを正しく提供しないか、単に再帰呼び出しを実行しようとするだけで)、そのためのスペースを提供するための十分なスペースがなく、最終的にStackOverflow
になります。 。
ループにこの問題がない理由は、ループの各反復で独自のスペースが使用されないためです(つまり、n
回ループする場合、n+1
stループを実行するために余分なスペースは必要ありません)。
再帰のレベルが下がると、ランタイムスタックに状態情報が追加されます。この情報はアクティブ化レコードに格納され、スコープ内の変数やその値などの情報が含まれています。ループには、ループするたびに余分なアクティブ化レコードがないため、メモリの消費量が少なくなります。
特定の状況では、再帰が深くなりすぎてスタックがオーバーフローする可能性がありますが、これが起こらないようにする方法があります。再帰を使用するときは、通常、次の形式に従います。
public obj MyMethod(string params) {
if (base-case) {
do something...
} else {
do something else...
obj result = MyMethod(parameters here);
do something else if needed..
}
}
再帰は非常に効果的で、ループではできないことを実行できます。ときどき、再帰が明白な決定であるところまでたどり着きます。優れたプログラマーになる理由は、完全に目立たない場合でもそれを使用できることです。
メソッドを呼び出すたびに、スタックから「フレーム」を消費します。このフレームは、メソッドが戻るまで解放されません。ループでは同じことが起こりません。
再帰によってスタックオーバーフローが発生する理由は、再帰を停止するタイミングを確立できないため、関数/メソッドは(エラーが発生するまで)自分自身を「永久に」呼び出し続けるためです。ループを使用している場合でも、次のような場合は同じ問題が発生します。
bool flag = true;
while (flag == true){
count++;
}
flag
は常にtrueであるため、whileループはスタックオーバーフローエラーが発生するまで停止しません。
適切に使用すると、再帰はStackOverflowError
を生成しません。一致する場合、ベースケースはトリガーされておらず、メソッドはそれ自体を無限に呼び出し続けます。完了しないすべてのメソッド呼び出しはスタックに残り、最終的にはオーバーフローします。
ただし、ループ自体はメソッド呼び出しを含まないため、スタックには何も構築されず、StackOverflowError
は発生しません。
再帰によりスタックオーバーフローが発生し、以前のすべての呼び出しがメモリ内に存在します。そのため、メソッドは新しいパラメータを使用して自身を呼び出し、次に再びそれ自体を呼び出します。そのため、これらの呼び出しはすべてスタックし、通常はメモリ不足になる可能性があります。ループは通常一部の変数に結果を格納し、メソッドへの新しい新鮮な呼び出しのようなメソッドを呼び出します。各呼び出しの後、呼び出し元のメソッドは終了し、結果を返します。