2009-05-13 6 views
20

La récursion est une sorte de style « diviser pour mieux régner », il se sépare tout en obtenant plus petite (structure de données d'arbre), et je le veux briser complètement si une violation est constatée, ce qui signifie la rupture tous les chemins récursifs, et retour vrai. Est-ce possible?rupture d'un récursivité en java

+0

@Stasis: Que voulez-vous dire par « tous les chemins récursives ». Il semble que vous manquiez comment la récursivité fonctionne. Remarquez que si vous avez une structure arborescente que vous parcourez récursivement, vous n'explorez pas simultanément les branches de l'arbre ... vous l'explorez 1 nœud à la fois.Donc, faire quelque chose comme la réponse de Tom fonctionnerait. Si vous utilisiez plusieurs threads pour traiter différentes branches, vous devez conserver un état global, mais je ne pense pas que ce soit ce que vous aviez en tête. Nous aimons penser à la récursivité comme explorant de nombreux chemins, mais comprenons simplement que cela ne se produit pas à la fois. – Tom

+0

Pour clarifier, j'ai écrit le commentaire ci-dessus, se référant à un autre Tom qui a posté une réponse. – Tom

+0

Oui, mais ce à quoi je pensais d'abord, c'est de terminer cette "branche" récursive, par l'absence d'un meilleur mot, et au lieu d'aller chez l'autre pour se terminer, prétendre que ça n'arrive pas et renvoyer faux, plutôt que des entiers; Ou quelque chose de tel. – Stasis

Répondre

14

Vous pouvez renvoyer un code d'erreur, ou modifier une variable globale de sorte que chaque instance récursive sait « se tuer ».

Quelque chose du genre.

int foo(bar){ 
    int to_the_next; 

     if (go_recursive){ 
      to_the_next = foo(whisky_bar); 

      if (to_the_next ==DIE) return DIE; 
     } 

     if (something_unexpected_happened) return DIE; 

     process;//may include some other recursive calls, etc etc 
} 
+1

Hmm merci, j'ai pensé à quelque chose comme ça, mais je voulais juste voir s'il y avait un moyen de tuer toutes les étapes récursives, mais je vais juste aller avec ça. – Stasis

+0

Eh bien, techniquement, cela pourrait être fait. Si vous saviez où se trouve votre fonction d'origine, vous pouvez sauter dans ce cadre de pile et prétendre que rien ne s'est passé. Cependant, il faut programmer l'assemblage (je ne vois pas d'autre moyen de le faire) – Tom

+0

Oui, je pourrais le faire en x68, mais ce projet doit être fait en Java><, et je ne saurais pas vraiment comment approfondir empiler le cadre en Java. – Stasis

1

Vous pouvez faire quelque chose de similaire en stockant une variable qui suit si les récurrences devraient briser ou non. Il faudrait malheureusement vérifier chaque récursion, mais vous pouvez le faire.

5

Si c'est un seul thread faire la récursion, vous pouvez lancer une exception. Peu moche si - sorte d'utiliser une exception comme un goto.

boolean myPublicWrapperMethod(...) { 
    try { 
     return myPrivateRecursiveFunction(...); 
    } catch (MySpecificException e) { 
     return true; 
    } 
} 

Une meilleure approche serait d'éliminer la récursion et utiliser une collection Stack tenue d'une classe représentant ce qui aurait été l'état récursif, itérer dans une boucle et juste revenir vrai quand vous voulez.

0

À moins que les appels récursifs soient évalués en parallèle, il suffit probablement d'ajouter une logique pour vérifier la valeur du premier appel récursif avant d'effectuer le second appel récursif (et éventuellement un arbre binaire).

public abstract class Tree { 

    protected abstract boolean headIsViolation(); 

    protected abstract boolean isLeaf(); 

    public Tree getLeft(); 

    public Tree getRight(); 

    // Recursive 
    public boolean checkViolation() { 
     if(this.isLeaf()) { 
      return this.headIsViolation(); 
     } 

     // If needed, you could pass some sort of 'calculation state' 
     // object through the recursive calls and evaluate the violation 
     // through that object if the current node is insufficient 
     if(this.headIsViolation()) { 
      // Terminate the recursion 
      return true; 
     } 

     // Fortunately, Java short-circuits || 
     // So if the left child is in violation, the right child will 
     // not even be checked 
     return this.getLeft().checkViolation() || this.getRight().checkViolation(); 
    } 
} 
34

Peu importe ce que vous faites, vous allez devoir dérouler la pile. Cela laisse deux options:

  1. valeur de retour magique (tel que décrit par l'un des Toms)
  2. Throw une exception (comme mentionné par thaggie)

Si le cas où vous voulez que les choses meurent est rare, cela peut être une de ces situations où lancer une exception peut être un choix viable. Et avant que tout le monde me saute à la gorge, rappelez-vous que l'une des règles de programmation les plus importantes est de savoir quand il est approprié d'enfreindre la règle.

Comme il se trouve, j'ai passé l'évaluation aujourd'hui la bibliothèque ZXing de code Google. Ils utilisent réellement des jets d'exception pour beaucoup de structures de contrôle. Ma première impression quand je l'ai vu était horreur. Ils appelaient littéralement les méthodes des dizaines de milliers de fois avec des paramètres différents jusqu'à ce que la méthode ne lance pas d'exception.

Cela ressemblait certainement un problème de performance, donc je fait quelques ajustements pour changer les choses sur à l'aide d'une valeur de retour magique. Et tu sais quoi? Le code était 40% plus rapide lors de l'exécution dans un débogueur. Mais quand je suis passé au non-débogage, le code était moins de 1% plus rapide.

Je ne suis toujours pas fou de la décision d'utiliser des exceptions pour le contrôle de flux dans ce cas (je veux dire, les exceptions sont jetés tout le temps). Mais cela ne vaut certainement pas la peine de le ré-implémenter compte tenu de la différence de performance presque incommensurable.

Si votre condition qui déclenche la mort de l'itération est pas un élément fondamental de l'algorithme, en utilisant une exception peut rendre votre code un beaucoup plus propre.Pour moi, le point où je prendrais cette décision est que si toute la récursion doit être annulée, alors j'utiliserais une exception. SI seule une partie de la récursion doit être déroulée, utilisez une valeur de retour magique.

+0

L'OP écrit "Je veux qu'il se brise complètement si une violation est trouvée", ce qui me semble être une "condition exceptionnelle", et c'est exactement ce que sont les exceptions. – DJClayworth

+3

(Je suis l'auteur du code zxing) Si le contrat d'une méthode est "renvoyer le code-barres trouvé dans cette ligne", que voudriez-vous qu'elle renvoie quand elle n'existe pas? 'null' est un cop-out, puisqu'il ne retourne rien. Une exception est appropriée. Mais votre point était la performance. L'autre auteur m'a mis au défi à ce sujet initialement. Si vous creusez dans le code, vous verrez que nous utilisons une exception singleton pour contourner les problèmes de performances. –

+1

Je partirais toujours de la position que la performance dans un état exceptionnel n'a pas d'importance. Si le profilage me montrait le contraire, je changerais d'avis. – DJClayworth

1

Ce que vous demandez, c'est la définition de la récursivité.

À un certain point, tous les chemins récursifs devraient se rompre. Sinon, ce sera une récursion infinie et stack overflow exception se produit.

Donc, vous devriez concevoir la fonction de récursion comme ça. Un exemple binary search in a sorted array:

BinarySearch(A[0..N-1], value, low, high) { 
     if (high < low) 
      return -1 // not found 
     mid = low + ((high - low)/2) // Note: not (low + high)/2 !! 
     if (A[mid] > value) 
      return BinarySearch(A, value, low, mid-1) 
     else if (A[mid] < value) 
      return BinarySearch(A, value, mid+1, high) 
     else 
      return mid // found 
} 
3

Je recommanderais une gestion des exceptions. Cela indique clairement, que la récursivité a été annulée en raison d'une violation (ou autre exception):

public void outer() { 
    try { 
    int i = recurse(0); 
    } catch (OuchException ouch) { 
    // handle the exception here 
    } 
} 

private int recurse(int i) throws OuchException { 

    // for tree-like structures 
    if (thisIsALeaf) { 
     return i; 
    } 

    // the violation test 
    if (itHurts) 
     throw new OuchException("That really hurts"); 

    // we also can encapsulate other exceptions 
    try { 
     someMethod(); 
    } catch (Exception oops) { 
     throw new OuchException(oops); 
    } 

    // recurse 
    return recurse(i++); 
}

Bien sûr, il viole l'exigence initiale de revenir « vraie » sur l'avortement. Mais je préfère une séparation nette des valeurs de retour et une notification sur les comportements anormaux.

0

J'ai été capable de quitter tous les appels récursifs en utilisant une variable globale.

boolean skipRecursivePaths = false; 
private callAgain(){ 

if(callAgain){ 
    if(getOutCompletely){ 

    skipRecursivePaths = true; 
    } 
    if(skipRecursivePaths){ 
    return; 
    } 
} 
0

La meilleure façon de sortir d'une boucle récursive lorsqu'une erreur est rencontrée est de lancer une exception d'exécution.

throw new RuntimeException("Help! Somebody debug me! I'm crashing!"); 

Bien sûr, cela tue votre programme, mais vous devez utiliser l'analyse de la vérification et de l'algorithme gamme pour vous assurer que votre récursion ne jette pas une telle exception. Une raison pour laquelle vous pourriez vouloir sortir d'un algorithme récursif est que vous manquez de mémoire. Ici, il est possible de déterminer la quantité de mémoire que votre algorithme utilisera sur la pile. Si vous codez Java, par exemple, comparer ce calcul à

getMemoryInfo.availMem(). 

Disons que vous utilisez récursion pour trouver n !. Votre fonction ressemble à ceci:

public long fact(int n) 
{ 
    long val = 1; 
    for (int i = 1, i<=n,i++) 
     return val *= fact(i); 
    return val; 
} 

avant de l'exécuter, vérifiez que vous avez (nombre d'octets dans une longue, 8 en Java) * n octets en mémoire pour maintenir l'ensemble de la pile. Fondamentalement, avec la vérification de distance et d'erreur avant la méthode/fonction récursive, vous ne devriez pas avoir besoin de sortir. Selon votre algorithme, cependant, vous devrez peut-être signaler à toute la pile que vous êtes prêt à partir. Si c'est le cas, la réponse de Tom fonctionne.

-1

mieux d'avoir un tableau booléen

cette façon il n'y a pas état global

if(stop[0]) return; 
0

Peut-être que vous voulez éviter la récursivité et le remplacer par une pile. Cela vous donne le contrôle de sortir de l'opération, tout en étant capable de faire quelque chose qui ressemble à une opération récursive. En fait, vous ne faites qu'émuler la récursivité par vous-même.

J'ai trouvé une explication bien ici: http://haacked.com/archive/2007/03/04/Replacing_Recursion_With_a_Stack.aspx/

Questions connexes