2010-06-21 14 views
10

Attualmente sto lavorando in PHP, quindi questo esempio sarà in PHP, ma la domanda si applica a più lingue.Qual è una differenza pratica tra un loop e la ricorsione

Sto lavorando a questo progetto con un mio demone, e come sempre siamo stati ostacolati da un grosso problema. Ora siamo andati entrambi a casa, non siamo riusciti a risolvere il problema. Quella sera entrambi abbiamo trovato la soluzione, solo che ho usato un ciclo per affrontare il problema, e ha usato la ricorsione.

Ora volevo dirgli la differenza tra il ciclo e la ricorsione, ma non sono riuscito a trovare una soluzione in cui è necessario ricorrere su un ciclo normale.

Sto per fare una versione semplificata di entrambi, spero che qualcuno possa spiegare come uno è diverso dall'altro.

prego di perdonarmi per eventuali errori di codifica

Il loop:

printnumbers(1,10); 

public function printnumbers($start,$stop) 
{ 
    for($i=$start;$i<=$stop;$i++) 
    { 
     echo $i; 
    } 
} 

Ora il codice di cui sopra semplicemente stampa i numeri.

Ora facciamo questo con la ricorsione:

printnumbers(1,10); 

public function printnumbers($start,$stop) 
{ 
    $i = $start; 
    if($i <= $stop) 
    { 
     echo $i; 
     printnumbers($start+1,$stop); 
    } 
} 

Questo metodo di cui sopra farà la stessa cosa come il ciclo, ma poi solo con la ricorsione.

Qualcuno può spiegarmi cosa c'è di diverso nell'usare uno di questi metodi.

+0

Hmm, perché stai contando all'indietro nel secondo snippet di codice? –

+0

Oh scusa è solo un refuso, risolto! –

+0

Penso che mostri una ragione per essere cauti riguardo alle funzioni ricorsive: possono essere difficili da ottenere e talvolta portano a questo stesso sito. –

risposta

12

I loop e le ricorsioni sono in molti modi equivalenti. Non ci sono programmi che richiedono l'uno o l'altro, in linea di principio si può sempre tradurre da loop a ricorsione o viceversa.

Le ricorsioni sono più potenti nel senso che tradurre la ricorsione in un ciclo potrebbe richiedere uno stack da manipolare. (Prova ad attraversare un albero binario usando un loop e sentirai il dolore.)

D'altra parte, molte lingue (e implementazioni), ad es. Java, non implementano correttamente la ricorsione della coda. La ricorsione della coda è quando l'ultima cosa che fai in una funzione è chiamarti (come nel tuo esempio). Questo tipo di ricorsione non deve consumare alcuno stack, ma in molte lingue lo fanno, il che significa che non è sempre possibile ricorrere alla ricorsione.

+0

Il problema in quei linguaggi che non supporta la ricorsione in coda è che lo stack è spesso piuttosto piccolo, causando lo stackoverflow su strutture di grandi dimensioni. Nelle lingue Java o C# è necessario utilizzare uno stack esplicito anziché ricorsivo (su strutture grandi o sconosciute). – Moberg

+0

Non ho mai pensato che la differenza tra questi due fosse così minima. Esistono solo alcuni problemi di funzionalità linguistica e problemi di prestazioni. Pensavo che ci sarebbero state molte differenze pratiche, suppongo di aver sbagliato. –

0

Rispetto ai cicli, una chiamata di funzione ha il proprio overhead come allocare stack ecc. E nella maggior parte dei casi, i loop sono più comprensibili rispetto alle controparti ricorsive.

Inoltre, si finirà per utilizzare più memoria e si può anche esaurire lo spazio di stack se la differenza tra avvio e arresto è elevata e ci sono troppe istanze di questo codice in esecuzione contemporaneamente (il che può accadere quando si ottiene più traffico).

1

Bene, non conosco PHP ma la maggior parte delle lingue genera una chiamata di funzione (a livello macchina) per ogni ricorsione. Quindi hanno il potenziale per utilizzare un sacco di spazio nello stack, a meno che il compilatore non produca ottimizzazioni tail-call (se il tuo codice lo consente). I loop sono più "efficienti" in questo senso perché non aumentano lo stack. La ricorsione ha il vantaggio di essere in grado di esprimere alcuni compiti in modo più naturale però.

In questo caso specifico, da un punto di vista concettuale (piuttosto che implementativo), le due soluzioni sono totalmente equivalenti.

2

In generale, una funzione ricorsiva consumerà più spazio nello stack (poiché è in realtà un ampio insieme di chiamate di funzione), mentre una soluzione iterativa non lo farà. Ciò significa anche che una soluzione iterativa, in generale, sarà più veloce perché.

Non sono sicuro che se questo si applica a un linguaggio interpretato come PHP, è possibile che l'interprete possa gestirlo meglio.

2

Un ciclo sarà più veloce perché c'è sempre un sovraccarico nell'esecuzione di una chiamata di funzione extra.

Un problema con l'apprendimento della ricorsione è che molti degli esempi forniti (diciamo, fattoriali) sono esempi negativi di utilizzo della ricorsione.

Ove possibile, seguire un ciclo a meno che non sia necessario fare qualcosa di diverso. Un buon esempio di utilizzo della ricorsione è il loop su ciascun nodo in una struttura con più livelli di nodi figlio.

+1

+1 per: "Se possibile, segui un ciclo a meno che non sia necessario fare qualcosa di diverso". Non ho mai veramente capito il vero scopo della ricorsione, questo perché non ho mai saputo se la soluzione sarebbe stata migliore usando la ricorsione. Ora so che la ricorsione è una soluzione a un problema in cui un loop non è sufficiente. –

+0

@SaifBechan: non sono d'accordo con il dogma di stare con i loop; la ricorsione riflette spesso il problema meglio dei cicli, tentando di forzare l'adattamento di un problema ricorsivo nell'iterazione porta spesso a codice complesso e difficile da capire (e viceversa). –

+0

Nessuno ha detto nulla sul dogma. Nella maggior parte dei casi, probabilmente non hai bisogno di ricorsione. –

2

La ricorsione è un po 'più lenta (perché le chiamate di funzione sono più lente rispetto all'impostazione di una variabile) e utilizza più spazio nella maggior parte degli stack di chiamate delle lingue. Se si è provato a printnumbers(1, 1000000000), la versione ricorsiva potrebbe generare un errore fatale PHP o addirittura un errore 500.

Ci sono alcuni casi in cui la ricorsione ha senso, come fare qualcosa per ogni parte di un albero (ottenere tutti i file in una directory e le sue sottodirectory, o magari fare scherzi con un documento XML), ma ha il suo prezzo - in velocità, stack footprint e il tempo speso per assicurarsi che non si blocca più e più volte fino a quando si blocca. Se un loop ha più senso, è sicuramente la strada da percorrere.

+3

La ricorsione non è sempre più lenta. La velocità dipende dall'implementazione della lingua in uso. – oherrala

+0

L'unica volta che non è più lento è quando il linguaggio/compilatore può eseguire l'ottimizzazione delle chiamate tail. Anche allora, non l'ho mai visto essere più veloce. Una chiamata di funzione prevede la memorizzazione di parametri (o puntatori a parametri), la selezione dell'indirizzo di ritorno e il salto alla funzione. Questo è in * qualsiasi * linguaggio che supporta le funzioni, perché è praticamente ciò che una funzione è - una lista di istruzioni con un punto di ingresso definito, (opzionalmente) un modo per prendere parametri (che, nelle lingue che supportano la ricorsione, richiede praticamente una pila) e un modo per tornare indietro. In confronto, un ciclo fa un negozio e un salto. – cHao

+0

L'ottimizzazione della coda è solo un travestimento. E l'unico modo in cui la ricorsione anche * potrebbe * essere più veloce del looping è se il tuo linguaggio vacilla in loop - nel qual caso, non lo userò comunque. – cHao

8

Spesso, un problema è più semplice espresso utilizzando la ricorsione. Ciò è particolarmente vero quando parli di strutture di dati ad albero (ad es. Directory, alberi decisionali ...).

Queste strutture dati sono di natura finita, quindi il più delle volte elaborandole è più chiara con la ricorsione.

Quando la profondità dello stack è spesso limitata e ogni chiamata di funzione richiede un pezzo di stack e quando si parla di una struttura di dati potenzialmente infinita, si dovrà abbandonare la ricorsione e tradurla in iterazione.

I linguaggi particolarmente funzionali sono efficaci nella gestione della ricorsione "infinita". Le lingue imperative sono focalizzate su cicli simili a iterazioni.

+0

+1 Grazie per l'elettricità statica, le strutture simili a quelle dell'albero. Posso immaginare come la ricorsione sia l'ideale per questa situazione, dato che puoi facilmente saltare in un altro punto dell'albero. –

+0

@xtofi Ottima spiegazione! Potresti - o qualcun altro - essere in grado di elaborare o indicarmi un po 'di letteratura o un link per elaborare la tua ultima affermazione? "I linguaggi particolarmente funzionali sono efficaci nella gestione della ricorsione" infinita ". Le lingue imperative sono focalizzate su cicli simili a iterazioni." – Anovative

+1

@Anovative: cerca "coda ricorsione" o "ottimizzazione chiamata coda". – xtofl

0

Non hai davvero bisogno di ricorsione per una struttura piatta come quella. Il primo codice che ho mai usato per la ricorsione riguarda la gestione dei contenitori fisici. Ogni contenitore potrebbe contenere elementi (un elenco di essi, ciascuno con pesi) e/o più contenitori, che hanno un peso. Avevo bisogno del peso totale di un contenitore e di tutto ciò che conteneva. (Lo usavo per prevedere il peso di grandi zaini pieni di attrezzature da campeggio senza imballarli e pesarli.) Questo era facile da fare con la ricorsione e sarebbe stato molto più difficile con i loop. Ma molti tipi di problemi che naturalmente si adattano a un approccio possono anche essere affrontati con l'altro.

1

Overflow dello stack.

E no, non intendo un sito Web o qualcosa del genere. Intendo un "overflow dello stack".

Problemi correlati