Modo generale per convertire un ciclo (while / for) in ricorsione o da una ricorsione in un ciclo?


23

Questo problema si concentra principalmente sull'algoritmo, forse qualcosa di astratto e più accademico.

L'esempio offre un pensiero, voglio un modo generico, quindi l'esempio è usato solo per renderci più chiari sui tuoi pensieri.

In generale, un loop può essere convertito in ricorsivo.

per esempio:

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

E il relativo ricorsivo è:

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

E infine per semplificare questo, è necessario un ricorsivo di coda:

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

Tuttavia, la maggior parte dei casi non è così facile rispondere e analizzare. Quello che voglio sapere è:

1) Possiamo ottenere un "modo comune generale" per convertire un loop (per / mentre ……) in ricorsivo? E a quali tipi di cose dovremmo prestare attenzione mentre facciamo la conversione? Sarebbe meglio scrivere informazioni dettagliate con alcuni esempi e le tue teorie persudo, nonché il processo di conversione.

2) "Ricorsivo" ha due forme: ricorsivamente lineare e ricorsivo di coda. Quindi quale è meglio convertire? Quale "regola" dovremmo padroneggiare?

3) A volte abbiamo bisogno di mantenere la "storia" ricorsiva, questo può essere fatto facilmente in un'istruzione di ciclo:

per esempio:

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

Il risultato di seguito è:

Il risultato di 1 è 1.

Il risultato di 1 + 2 è 3.

Il risultato di 1 + 2 + 3 è 6 …………

Tuttavia, penso che sia difficile mantenere la cronologia in modo ricorsivo, perché un algoritmo basato su ricorsività si concentra su come ottenere l'ultimo risultato e fare un ritorno di richiamata. Quindi, tutto ciò avviene attraverso lo stack gestito dal linguaggio di programmazione che assegna automaticamente la memoria sotto forma di stack. E come possiamo "estrarre" manualmente ciascuno dei "valori dello stack" e restituire più valori attraverso un algoritmo ricorsivo?

E che dire di "da un algoritmo ricorsivo a un ciclo"? Possono essere convertiti l'uno nell'altro (penso che dovrebbe essere fatto teoricamente, ma voglio cose più precise per dimostrare i miei pensieri) .


cosa significa "persudo"?
moscerino

Risposte:


30

In realtà dovresti prima disattivare la funzione:

Un ciclo ha alcune parti:

  1. l'intestazione e l'elaborazione prima del ciclo. Può dichiarare alcune nuove variabili

  2. la condizione, quando interrompere il loop.

  3. l'effettivo corpo del loop. Cambia alcune delle variabili dell'intestazione e / o i parametri passati.

  4. la coda; cosa succede dopo il loop e il risultato di ritorno.

O per scriverlo:

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

L'uso di questi blocchi per effettuare una chiamata ricorsiva è piuttosto semplice:

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 versione ricorsiva della coda di qualsiasi loop. breaks e continues nel corpo del loop dovranno comunque essere sostituiti return taile restituiti foo_recursion(params, modified_header_vars)secondo necessità, ma è abbastanza semplice.


Andare dall'altra parte è più complicato; in parte perché possono esserci più chiamate ricorsive. Ciò significa che ogni volta che pop un frame stack ci possono essere più posti in cui dobbiamo continuare. Inoltre, potrebbero esserci delle variabili che dobbiamo salvare attraverso la chiamata ricorsiva e i parametri originali della chiamata.

Possiamo usare un interruttore per aggirare quello:

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

0

Seguendo la risposta di @ratchet maniaco, ho creato questo esempio di come la funzione Fibonacci può essere riscritta in un ciclo while in Java. Nota che c'è un modo molto più semplice (ed efficiente) per riscrivere 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;
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.