¿Forma general de convertir un bucle (while / for) en recursión o de una recursión a un bucle?

23

Este problema se centra principalmente en el algoritmo, quizás algo abstracto y más académico.

El ejemplo es ofrecer un pensamiento, quiero una forma genérica, por lo que el ejemplo solo se usa para hacernos más claros sobre sus pensamientos.

En términos generales, un bucle se puede convertir en recursivo.

p.ej:

for(int i=1;i<=100;++i){sum+=i;}

Y su recursivo relacionado es:

int GetTotal(int number)
{
   if (number==1) return 1;   //The end number
   return number+GetTotal(number-1); //The inner recursive
}

Y finalmente para simplificar esto, se necesita un recursivo de cola:

int GetTotal (int number, int sum)
{
    if(number==1) return sum;
    return GetTotal(number-1,sum+number);
}

Sin embargo, la mayoría de los casos no son tan fáciles de responder y analizar. Lo que quiero saber es:

1) ¿Podemos obtener una "forma común general" para convertir un bucle (por / mientras ......) en un recursivo? ¿Y a qué tipo de cosas debemos prestar atención mientras hacemos la conversión? Sería mejor escribir información detallada con algunas muestras y sus teorías persuasivas, así como el proceso de conversión.

2) "Recursivo" tiene dos formas: Linely recursive y Tail-Recursive. Entonces, ¿cuál es mejor para convertir? ¿Qué "regla" debemos dominar?

3) A veces necesitamos mantener el "historial" de recursivo, esto se puede hacer fácilmente en una declaración de bucle:

p.ej:

List<string> history = new List<string>();
int sum=0;
for (int i=1;i<=100;++i)
{
   if(i==1) history.Add(i.ToString()+"'s result is:1.");
   else
   {
     StringBuilder sub = new StringBuilder();

      for(int j=1;j<=i;++j)
      {
          if(j==i) sbu.Append(j.ToString());
          else
          {
            sub.Append(j.ToString()+"+");
          }
      }
    sum +=i;
    sbu.Append("'s result is:"+sum+Environment.NewLine);
   }
}

El resultado a continuación es:

El resultado de 1 es 1.

El resultado de 1 + 2 es 3.

El resultado de 1 + 2 + 3 es 6 …………

Sin embargo, creo que es difícil mantener el historial de forma recursiva, porque un algoritmo recursivo se enfoca en obtener el último resultado y hacer una devolución de llamada. Entonces, todo esto se hace a través de la pila mantenida por el lenguaje de programación que asigna memoria en forma de pila automáticamente. ¿ Y cómo podemos "manualmente" sacar cada uno de los "valores de la pila" y devolver múltiples valores a través de un algoritmo recursivo?

¿Y qué hay de "de un algoritmo recursivo a un bucle"? ¿Se pueden convertir entre sí? (Creo que debería hacerse teóricamente, pero quiero cosas más precisas para probar mis pensamientos) .

xqMogvKW
fuente
¿Qué significa "persudo"?
mosquito

Respuestas:

30

En realidad, primero debes descomponer la función:

Un bucle tiene algunas partes:

  1. el encabezado y el procesamiento antes del bucle. Puede declarar algunas variables nuevas.

  2. la condición, cuándo detener el ciclo.

  3. El cuerpo del bucle real. Cambia algunas de las variables del encabezado y / o los parámetros pasados.

  4. la cola; qué sucede después del bucle y devuelve el resultado.

O para escribirlo:

foo_iterative(params){
    header
    while(condition){
        loop_body
    }
    return tail
}

Usar estos bloques para hacer una llamada recursiva es bastante sencillo:

foo_recursive(params){
    header
    return foo_recursion(params, header_vars)
}

foo_recursion(params, header_vars){
    if(!condition){
        return tail
    }

    loop_body
    return foo_recursion(params, modified_header_vars)
}

Et voilà; Una versión recursiva de cola de cualquier bucle. breaks y continues en el cuerpo del bucle aún tendrán que ser reemplazados return taily regresar foo_recursion(params, modified_header_vars)según sea necesario, pero eso es bastante simple.


Ir hacia el otro lado es más complicado; en parte porque puede haber múltiples llamadas recursivas. Esto significa que cada vez que hacemos estallar un marco de pila puede haber múltiples lugares donde necesitamos continuar. También puede haber variables que necesitamos guardar en la llamada recursiva y los parámetros originales de la llamada.

Podemos usar un interruptor para evitar eso:

bar_recurse(params){
    if(baseCase){
        finalize
        return
    }
    body1
    bar_recurse(mod_params)
    body2
    bar_recurse(mod_params)
    body3
}


bar_iterative(params){
    stack.push({init, params})

    while(!stack.empty){
        stackFrame = stack.pop()

        switch(stackFrame.resumPoint){
        case init:
            if(baseCase){
                finalize
                break;
            }
            body1
            stack.push({resum1, params, variables})
            stack.push({init, modified_params})
            break;
        case resum1:
            body2
            stack.push({resum2, params, variables})
            stack.push({init, modified_params})
            break;
        case resum2:
            body3
            break;
        }
    }
}
monstruo de trinquete
fuente
0

Siguiendo con la respuesta de @ratchet freak, creé este ejemplo de cómo la función Fibonacci se puede reescribir en un ciclo while en Java. Tenga en cuenta que hay una forma mucho más simple (y eficiente) de reescribir el Fibonacci con un ciclo while.

class CallContext { //this class is similar to the stack frame

    Object[] args;

    List<Object> vars = new LinkedList<>();

    int resumePoint = 0;

    public CallContext(Object[] args) {
        this.args = args;
    }

}


static int fibonacci(int fibNumber) {
    Deque<CallContext> callStack = new LinkedList<>();
    callStack.add(new CallContext(new Object[]{fibNumber}));
    Object lastReturn = null; //value of last object returned (when stack frame was dropped)
    while (!callStack.isEmpty()) {
        CallContext callContext = callStack.peekLast();
        Object[] args = callContext.args;
        //actual logic starts here
        int arg = (int) args[0];
        if (arg == 0 || arg == 1) {
            lastReturn = arg;
            callStack.removeLast();
        } else {
            switch (callContext.resumePoint) {
                case 0: //calculate fib(n-1)
                    callStack.add(new CallContext(new Object[]{arg - 1}));
                    callContext.resumePoint++;
                    break;
                case 1: //calculate fib(n-2)
                    callContext.vars.add(lastReturn); //fib1
                    callStack.add(new CallContext(new Object[]{arg - 2}));
                    callContext.resumePoint++;
                    break;
                case 2: // fib(n-1) + fib(n-2)
                    callContext.vars.add(lastReturn); //fib2
                    lastReturn = (int) callContext.vars.get(0) + (int) callContext.vars.get(1);
                    callStack.removeLast();
                    break;
            }
        }
    }
    return (int) lastReturn;
}
Yamcha
fuente