web-dev-qa-db-fra.com

java.lang.StackOverflowError en raison d'une récursion

Mon problème est que je reçois généralement une erreur Java.lang.StackOverflowError lorsque j'utilise la récursivité . Ma question est la suivante: pourquoi la récursivité provoque-t-elle autant de dépassements de pile que de boucles et existe-t-il un bon moyen d'utiliser la récursivité pour éviter les dépassements de pile ?

Ceci est une tentative pour résoudre problème 107 , cela fonctionne bien pour leur exemple mais manque d’espace de pile pour le problème lui-même.

//-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

pourquoi la récursivité provoque-t-elle beaucoup plus de boucles de débordement que de boucles

Parce que chaque appel récursif utilise de l’espace sur la pile. Si votre récursion est trop profonde, alors StackOverflow sera obtenu, en fonction de la profondeur maximale autorisée dans la pile.

Lorsque vous utilisez la récursivité, vous devez être très prudent et vous assurer de fournir un cas de base . Un cas de base en récurrence est la condition selon laquelle la récursivité se termine et la pile commence à se dérouler. C’est la principale raison de la récursivité qui cause une erreur StackOverflow. S'il ne trouve aucun cas de base, il entrera dans une récursion infinie, ce qui entraînera certainement une erreur, car Stack est fini seulement.

18
Rohit Jain

Dans la plupart des cas, un débordement de pile se produit car une méthode récursive a été mal définie, avec une condition de fin inexistante ou inaccessible, ce qui entraîne l'épuisement de l'espace mémoire de la pile. Une récursion correctement écrite ne doit pas entraîner de débordement de pile.

Cependant, il existe des situations dans lesquelles une méthode peut produire un débordement de pile même si elle a été correctement implémentée. Par exemple:

  • Une récursion à croissance rapide (par exemple exponentielle). Par exemple: l'implémentation récursive naïve de la fonction de Fibonacci
  • Une très grande donnée d'entrée, qui finira par épuiser l'espace de la pile

En bout de ligne: tout dépend du cas particulier, il est impossible de généraliser en ce qui concerne les causes d'un débordement de pile.

3
Óscar López

Chaque appel récursif utilise un espace sur la pile (pour stocker tout ce qui est spécifique à cet appel, tel que des arguments, des variables locales, etc.). Ainsi, si vous effectuez trop d'appels récursifs (soit en ne fournissant pas correctement un scénario de base, soit en essayant de faire trop d'appels récursifs), il n'y a plus assez de place pour fournir tout l'espace nécessaire et vous vous retrouvez avec un StackOverflow .

La raison pour laquelle les boucles n’ont pas ce problème est que chaque itération d’une boucle n’utilise pas son propre espace unique (c’est-à-dire si je boucle n fois, je n’ai pas besoin d’espace supplémentaire pour effectuer la boucle n+1st).

3
Dennis Meng

Chaque fois que vous appelez une méthode, vous consommez une "image" de la pile. Cette image n'est pas publiée tant que la méthode n'est pas renvoyée. Cela ne se produit pas de la même manière avec les boucles.

1
morgano

La raison pour laquelle la récursion provoque un dépassement de pile est due au fait que nous ne parvenons pas à déterminer quand la récursivité doit cesser. La fonction/méthode continuera donc à s’appeler "pour toujours" (jusqu’à ce qu’elle provoque l’erreur). Vous aurez le même problème même si vous utilisez des boucles, si vous avez quelque chose comme ceci:

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

Puisque flag sera toujours vrai, la boucle while ne s’arrêtera jamais tant qu’elle ne provoquera pas l’erreur de débordement de pile.

0
Anna

À chaque niveau de récursivité que vous baissez, vous ajoutez des informations d'état à la pile d'exécution. Ces informations sont stockées dans un enregistrement d'activation et contiennent des informations telles que les variables qui sont dans la portée et quelles sont leurs valeurs. Les boucles n’ont pas d’enregistrements d’activation supplémentaires à chaque boucle, elles utilisent donc moins de mémoire. 

Dans certaines situations, votre récursion peut aller suffisamment en profondeur pour que la pile déborde, mais il existe des moyens d’empêcher que cela se produise. Lorsque je travaille avec récursivité, je suis généralement le format suivant:

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

La récursivité peut être super efficace et faire des choses que les boucles ne peuvent pas. Parfois, vous arrivez à un point où la récursivité est la décision évidente. Ce qui fait de vous un bon programmeur, c'est de pouvoir l'utiliser quand ce n'est pas complètement obvoius.

0
TJS

Lorsqu'elle est correctement utilisée, la récursivité ne produira pas de StackOverflowError. Si tel est le cas, votre scénario de base n'est pas déclenché et la méthode continue à s'appeler à l'infini. Chaque appel de méthode qui ne se termine pas reste sur la pile et finit par déborder.

Mais les boucles n'impliquent pas d'appels de méthode par elles-mêmes, donc rien ne s'accumule sur la pile et il en résulte une StackOverflowError.

0
rgettman

la récursivité entraîne un débordement de la pile car tous les appels précédents sont en mémoire. donc votre méthode s’appelle avec de nouveaux paramètres, puis elle s’appelle à nouveau. de sorte que tous ces appels s'empilent et peuvent normalement manquer de mémoire . Les boucles stockent les résultats normalement dans certaines variables et appellent les méthodes, ce qui ressemble à un nouvel appel aux méthodes. Après chaque appel, les méthodes de l'appelant se terminent et renvoient les résultats .

0
Ashish Thukral