2009-05-13 15 views
20

La recursión es una especie de estilo 'divide y vencerás', se divide mientras se hace más pequeño (estructura de datos de árbol) y quiero que se rompa por completo si se encuentra una infracción, lo que significa ruptura todos los caminos recursivos, y devuelve verdadero. es posible?Activación de una recursión en java

+0

@Stasis: ¿Qué quiere decir con "todos los caminos recursivos". Parece que te estás perdiendo cómo funciona la recursividad. Observe que si tiene una estructura de árbol que está recorriendo recursivamente, no está explorando simultáneamente ramas del árbol ... lo está explorando 1 nodo a la vez.Entonces, hacer algo como la respuesta de Tom funcionaría. Si de alguna manera usabas varios hilos para procesar diferentes ramas, necesitarías mantener un estado global, pero no creo que eso es lo que tenías en mente. Nos gusta pensar que la recursividad explora muchos caminos, pero entiendo que esto no está sucediendo de inmediato. – Tom

+0

Para aclarar, escribí el comentario anterior, refiriéndome a un Tom diferente que publicó una respuesta. – Tom

+0

Sí, pero lo que estaba pensando al principio fue terminar esta "rama" recursiva, por la falta de una palabra mejor, y en lugar de ir a la otra para terminar, solo pretenda que no sucedió y devuelva falso, en lugar de enteros; O algo por el estilo. – Stasis

Respuesta

14

Puede devolver un código de error o modificar alguna variable global para que cada instancia recursiva sepa "suicidarse".

Algo del género.

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 gracias, pensé en algo así, pero solo quería ver si había una manera de matar todos los pasos recursivos, pero me iré con esto. – Stasis

+0

Bueno, técnicamente podría hacerse. Si supiera dónde se encuentra su función original, podría saltar a ese marco de pila y pretender que no pasó nada. Sin embargo, requiere programación de ensamblaje (no se puede pensar en otra forma de hacerlo) – Tom

+0

Sí, podría hacerlo en x68, pero este proyecto debe hacerse en Java><, y realmente no sabría cómo profundizar en él. apilar marcos en Java. – Stasis

1

Puede hacer algo similar almacenando una variable que rastree si las recursiones se deben romper o no. Tendría que comprobarlo lamentablemente cada recursión, pero puede hacerlo.

5

Si se trata de un único hilo que realiza la recursión, podría lanzar una excepción. Sin embargo, es un poco feo: algo así como usar una excepción como un goto.

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

Un mejor enfoque sería eliminar la recursividad y utilizar un conjunto de pila de una clase que representa lo que habría sido el estado recursiva, iterar en un bucle y sólo devuelve verdadero cuando lo desee.

0

A menos que las llamadas recursivas se evalúen en paralelo, probablemente solo necesite agregar algo de lógica para verificar el primer valor de la llamada recursiva antes de realizar la segunda (y subsiguiente, si no es una estructura de árbol binario) llamada recursiva.

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

No importa lo que hagas, vas a tener que desconectar la pila. Esto deja dos opciones:

  1. valor de retorno mágico (como se describe por uno de los Toms)
  2. lanzar una excepción (como se ha mencionado por thaggie)

Si el caso en el que desea que las cosas mueren es raro, esta puede ser una de esas situaciones donde arrojar una excepción podría ser una opción viable. Y antes de que todos se metan en la garganta, recuerde que una de las reglas más importantes de programación es saber cuándo es apropiado romper la regla.

Como resultado, pasé hoy evaluando la biblioteca zxing del código de google. En realidad usan lanzamientos de excepción para muchas estructuras de control. Mi primera impresión cuando lo vi fue horror. Literalmente llamaban a métodos decenas de miles de veces con diferentes parámetros hasta que el método no arrojaba una excepción.

Esto ciertamente parecía un problema de rendimiento, así que hice algunos ajustes para cambiar las cosas a usar un valor de retorno mágico. ¿Y sabes qué? El código era un 40% más rápido cuando se ejecutaba en un depurador. Pero cuando cambié a no depuración, el código era menos de 1% más rápido.

Todavía no estoy loco por la decisión de usar excepciones para el control de flujo en este caso (es decir, las excepciones se lanzan todos los el tiempo). Pero ciertamente no vale la pena el tiempo para volver a implementarlo dada la diferencia de rendimiento casi inconmensurable.

Si su condición que desencadena la muerte de la iteración no es una parte fundamental del algoritmo, usar una excepción puede hacer que su código sea mucho más limpio.Para mí, el punto donde tomaría esta decisión es si la recursión completa debe ser desenrollada, entonces usaría una excepción. SI solo se debe desenrollar parte de la recursión, use un valor de retorno mágico.

+0

El OP escribe "Quiero que se rompa por completo si se encuentra una violación", lo que me suena como una "condición excepcional", y es exactamente para qué sirven las excepciones. – DJClayworth

+3

(soy el autor del código zxing) Si el contrato de un método es "devolver el código de barras encontrado en esta fila", ¿qué le gustaría que devuelva cuando no exista uno? 'nulo' es un escape, ya que no devuelve nada. Una excepción es apropiada. Pero tu punto era el rendimiento. El otro autor me desafió en esto inicialmente. Si profundizas en el código, verás que usamos una excepción singleton para evitar problemas de rendimiento. –

+1

Siempre comenzaba desde la posición de que el rendimiento en una condición excepcional no importa. Si el perfil me mostrara lo contrario, cambiaría de opinión. – DJClayworth

1

Lo que está preguntando es la definición de recursividad.

En algún punto, todas las rutas recursivas deberían romperse. De lo contrario, se producirá una recursión infinita y se producirá stack overflow exception.

Entonces debería diseñar la función de recursión así. Un ejemplo 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

lo recomiendo un tratamiento de excepciones. Con ella se precisa, que la recursividad fue abortado debido a alguna violación (o otra excepción):

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++); 
}

Claro, se viola el requisito inicial para volver 'verdadero' sobre el aborto. Pero prefiero una separación limpia de los valores de retorno y una notificación sobre el comportamiento anormal.

0

Pude salir de todas las llamadas recursivas usando una variable global.

boolean skipRecursivePaths = false; 
private callAgain(){ 

if(callAgain){ 
    if(getOutCompletely){ 

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

La mejor manera de salir de un bucle recursivo cuando se detecta un error es lanzar una excepción de tiempo de ejecución.

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

Por supuesto, esto mata a su programa, pero usted debe emplear la verificación del rango y el análisis algoritmo para asegurarse de que su recursividad no produce una excepción. Una de las razones por las que podría querer salir de un algoritmo recursivo es que se está quedando sin memoria. Aquí, es posible determinar cuánta memoria usará tu algoritmo en la pila. Si está codificando Java, por ejemplo, comparar ese cálculo a

getMemoryInfo.availMem(). 

Digamos que usted está utilizando la recursividad para encontrar n !. Su función es como la siguiente:

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

Antes de que lo ejecute, compruebe que tiene (número de bytes en un largo, 8 en Java) * n bytes de memoria para contener toda la pila. Básicamente, con el rango y la verificación de errores antes del método/función recursiva, no debería necesitar salir. Dependiendo de tu algoritmo, sin embargo, es posible que debas indicarle a toda la pila que eres bueno para ir. Si ese es el caso, la respuesta de Tom funciona.

-1

mejor tener una matriz booleana

de esa manera no hay un estado mundial

if(stop[0]) return;