it-swarm.com.de

java.lang.StackOverflowError wegen Rekursion

Mein Problem ist, dass ich normalerweise einen Java.lang.StackOverflowError bekomme, wenn ich Rekursion verwende. Meine Frage ist: Warum verursacht Rekursion Stackoverflow so viel mehr als Schleifen, und gibt es eine gute Möglichkeit, Rekursion zu vermeiden? Paketüberfluss?

Dies ist ein Versuch, Problem 107 zu lösen, es funktioniert gut für ihr Beispiel, hat aber nicht genügend Speicherplatz für das Problem.

//-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&&copyOfMatrix[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;
    }
}
10
user2705335

warum verursacht Rekursion Stackoverflow so viel mehr als Schleifen

Weil jeder rekursive Aufruf etwas Speicherplatz auf dem Stack benötigt. Wenn Ihre Rekursion zu tief ist, führt dies abhängig von der maximal zulässigen Tiefe im Stapel zu StackOverflow.

Wenn Sie Rekursion verwenden, sollten Sie sehr vorsichtig sein und sicherstellen, dass Sie einen Basisfall angeben. Ein Basisfall in Rekursion ist die Bedingung, auf deren Grundlage die Rekursion endet und der Stapel beginnt, sich abzuwickeln. Dies ist der Hauptgrund für die Rekursion, die einen StackOverflow-Fehler verursacht. Wenn es keinen Basisfall findet, geht es in eine unendliche Rekursion über, die sicherlich zu Fehlern führt, da Stack nur endlich ist.

18
Rohit Jain

In den meisten Fällen tritt ein Stapelüberlauf auf, weil eine rekursive Methode schlecht definiert wurde und eine nicht vorhandene oder nicht erreichbare Endbedingung vorliegt, die dazu führt, dass der Stapelspeicherplatz erschöpft ist. Eine korrekt geschriebene Rekursion sollte keinen Stapelüberlauf erzeugen.

Es gibt jedoch Situationen, in denen eine Methode einen Stapelüberlauf erzeugen kann even , wenn sie korrekt implementiert wurde. Zum Beispiel:

  • Eine schnell wachsende (sagen wir exponentielle) Rekursion. Zum Beispiel: die naive rekursive Implementierung der Fibonacci-Funktion
  • Eine sehr große Eingabedaten, die letztendlich dazu führt, dass der Stapelspeicherplatz erschöpft ist

Fazit: Alles hängt vom Einzelfall ab. Es lässt sich nicht verallgemeinern, was einen Stack-Überlauf verursacht.

3
Óscar López

Jeder rekursive Aufruf benötigt etwas Speicherplatz auf dem Stack (um alles, was spezifisch für den einen Aufruf ist, wie Argumente, lokale Variablen usw.) aufzunehmen. Wenn Sie also zu viele rekursive Aufrufe tätigen (entweder indem Sie nicht richtig einen Basisfall angeben oder einfach zu viele rekursive Aufrufe ausführen), ist nicht genügend Platz vorhanden, um Platz für alles zu schaffen, und Sie erhalten eine StackOverflow. .

Der Grund, warum Schleifen dieses Problem nicht haben, besteht darin, dass für jede Iteration einer Schleife kein eigener eindeutiger Speicherplatz verwendet wird (d. H. Wenn ich n-Schleife durchführe, benötige ich keinen zusätzlichen Speicherplatz für die n+1st-Schleife).

3
Dennis Meng

Jedes Mal, wenn Sie eine Methode aufrufen, verbrauchen Sie einen "Frame" vom Stack. Dieser Frame wird erst freigegeben, wenn die Methode zurückgegeben wird. Bei Schleifen geschieht dies nicht dasselbe.

1
morgano

Der Grund, warum die Rekursion einen Stapelüberlauf verursacht, liegt darin, dass wir nicht feststellen können, wann die Rekursion aufhören sollte, und die Funktion/Methode sich selbst "für immer" aufruft (bis der Fehler verursacht wird). Sie haben dasselbe Problem, auch wenn Sie Schleifen verwenden, wenn Sie Folgendes haben:

bool flag = true;
while (flag == true){
   count++;
}

Da flag immer true ist, wird die while-Schleife nie angehalten, bis Sie den Stack-Überlauffehler erhalten.

0
Anna

Bei jeder Rekursionsebene, die Sie nach unten gehen, werden dem Laufzeitstapel Statusinformationen hinzugefügt. Diese Informationen werden in einem Aktivierungsdatensatz gespeichert und enthalten Informationen darüber, welche Variablen im Gültigkeitsbereich liegen und wie deren Werte sind. Schleifen enthalten nicht bei jeder Schleife zusätzliche Aktivierungsdatensätze, sodass weniger Speicherplatz erforderlich ist. 

In bestimmten Situationen kann Ihre Rekursion so tief gehen, dass der Stapel überläuft. Es gibt jedoch Möglichkeiten, dies zu verhindern. Wenn ich mit Rekursion arbeite, folge ich normalerweise diesem Format:

public obj MyMethod(string params) {
    if (base-case) {
        do something...
    } else {
        do something else...
        obj result = MyMethod(parameters here);
        do something else if needed..
    }
}

Rekursion kann sehr effektiv sein und Dinge tun, die Schleifen nicht können. Manchmal kommen Sie einfach zu einem Punkt, an dem Rekursion die offensichtliche Entscheidung ist. Was macht Sie zu einem guten Programmierer? Ist es in der Lage, es zu verwenden, wenn es nicht völlig obvoius ist.

0
TJS

Bei richtiger Verwendung erzeugt die Rekursion keine StackOverflowError. Wenn dies der Fall ist, wird Ihr Basisfall nicht ausgelöst, und die Methode ruft sich ad infinitum auf. Jeder Methodenaufruf, der nicht abgeschlossen wird, bleibt auf dem Stack und kann schließlich überlaufen.

Aber Schleifen beinhalten keine Methodenaufrufe selbst, so dass sich nichts auf dem Stack aufbaut und eine StackOverflowError nicht resultiert.

0
rgettman

rekursion verursacht einen Stapelüberlauf, da sich alle vorherigen Aufrufe im Speicher befinden. also ruft sich Ihre Methode mit neuen Parametern auf, dann ruft sich das wieder auf. Alle diese Aufrufe stapeln sich und normalerweise kann der Speicher ausgehen. Schleifen speichern die Ergebnisse normalerweise in einigen Variablen und rufen die Methoden auf. Dies ist wie ein neuer frischer Aufruf von Methoden. Nach jedem Aufruf werden die Aufrufermethoden und beendet gibt Ergebnisse zurück.

0
Ashish Thukral