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

20

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 suo ricorsivo correlato è:

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

Infine, per semplificare ciò, è 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 da rispondere e analizzare. Quello che voglio sapere è:

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

2) "Ricorsivo" ha due forme: ricorsiva lineare e ricorsiva della coda. Quindi, quale è meglio convertire? Quale "regola" dovremmo padroneggiare?

3) A volte abbiamo bisogno di mantenere la "cronologia" di ricorsiva, questo è facilmente eseguibile in un'istruzione di loop:

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 sotto è:

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 ricorsiva si concentra sull'ottenimento dell'ultimo risultato e sul ritorno della richiamata. Quindi, tutti questi sono fatti attraverso lo stack gestito dal linguaggio di programmazione che assegna automaticamente la memoria sotto forma di stack. E come possiamo "manualmente" prelevare ciascuno dei "valori dello stack" e restituire più valori tramite un algoritmo ricorsivo?

E che dire "da un algoritmo ricorsivo a un loop"? Possono essere convertiti l'un l'altro (penso che dovrebbe essere fatto teoricamente, ma voglio cose più accurate per provare i miei pensieri) .

    
posta xqMogvKW 14.04.2015 - 09:22
fonte

2 risposte

26

In realtà dovresti prima interrompere la funzione:

Un ciclo ha alcune parti:

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

  2. la condizione, quando interrompere il ciclo.

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

  4. la coda; cosa succede dopo il ciclo e restituisce il risultato.

O per scriverlo:

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

Utilizzare 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. break s e continue s nel corpo del ciclo dovranno essere sostituiti con return tail e restituiti foo_recursion(params, modified_header_vars) in base alle esigenze, ma è abbastanza semplice.

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

Possiamo usare un interruttore per ovviare a questo:

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;
        }
    }
}
    
risposta data 14.04.2015 - 10:04
fonte
0

Seguendo la risposta di @ratchet freak, 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 il 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;
}
    
risposta data 02.10.2017 - 21:11
fonte

Leggi altre domande sui tag