ricorsione contro iterazione


109

È corretto dire che ovunque si usa la ricorsione a for potrebbe ciclo? E se la ricorsione è solitamente più lenta, qual è la ragione tecnica per utilizzarla durante l' foriterazione del ciclo?

E se è sempre possibile convertire una ricorsione in un forciclo, esiste una regola pratica per farlo?


3
recursionvs iteration? iteration = for loopPenso.
gongzhitaao

4
Il blog di Tom Moertel ha quattro post eccellenti sulla conversione del codice ricorsivo in codice iterativo: blog.moertel.com/tags/recursion.html
cjohnson318

Risposte:


147

La ricorsione è solitamente molto più lenta perché tutte le chiamate di funzione devono essere memorizzate in uno stack per consentire il ritorno alle funzioni chiamante. In molti casi, la memoria deve essere allocata e copiata per implementare l'isolamento dell'ambito.

Alcune ottimizzazioni, come l' ottimizzazione della chiamata di coda , rendono le ricorsioni più veloci ma non sono sempre possibili e non sono implementate in tutte le lingue.

I motivi principali per utilizzare la ricorsione sono

  • che in molti casi è più intuitivo quando imita il nostro approccio al problema
  • che alcune strutture di dati come gli alberi sono più facili da esplorare usando la ricorsione (o avrebbero bisogno di stack in ogni caso)

Ovviamente ogni ricorsione può essere modellata come una sorta di loop: questo è ciò che alla fine farà la CPU. E la stessa ricorsione, più direttamente, significa mettere le chiamate di funzione e gli ambiti in uno stack. Ma cambiare il tuo algoritmo ricorsivo a uno a ciclo continuo potrebbe richiedere molto lavoro e rendere il tuo codice meno manutenibile: come per ogni ottimizzazione, dovrebbe essere tentata solo quando alcuni profili o prove hanno dimostrato che è necessario.


10
In aggiunta, la ricorsione è strettamente correlata al termine di riduzione che gioca un ruolo centrale in molti algoritmi e in CS in generale.
SomeWittyUsername

3
Potete fornirmi un esempio in cui la ricorsione rende il codice più gestibile? Nella mia esperienza, è sempre il contrario. Grazie
Yeikel

@Yeikel Scrivi una funzione f(n)che restituisca l'ennesimo numero di Fibonacci .
Matt

54

È corretto affermare che ovunque sia usata la ricorsione si potrebbe usare un ciclo for?

Sì, perché la ricorsione nella maggior parte delle CPU è modellata con loop e una struttura di dati dello stack.

E se la ricorsione è solitamente più lenta, qual è la ragione tecnica per utilizzarla?

Non è "solitamente più lento": è la ricorsione applicata in modo errato che è più lenta. Inoltre, i compilatori moderni sono bravi a convertire alcune ricorsioni in loop senza nemmeno chiedere.

E se è sempre possibile convertire una ricorsione in un ciclo for, esiste una regola pratica per farlo?

Scrivere programmi iterativi per algoritmi meglio compresi se spiegati in modo iterativo; scrivere programmi ricorsivi per algoritmi meglio spiegati ricorsivamente.

Ad esempio, la ricerca di alberi binari, l'esecuzione di quicksort e l'analisi di espressioni in molti linguaggi di programmazione vengono spesso spiegate in modo ricorsivo. Anche questi sono codificati al meglio in modo ricorsivo. D'altra parte, il calcolo dei fattoriali e il calcolo dei numeri di Fibonacci sono molto più facili da spiegare in termini di iterazioni. Usare la ricorsione per loro è come schiacciare le mosche con una mazza: non è una buona idea, anche quando la mazza fa un ottimo lavoro + .


+ Ho preso in prestito l'analogia con la mazza da "Discipline of Programming" di Dijkstra.


7
La ricorsione è solitamente più costosa (più lenta / più memoria), a causa della creazione di stack frame e simili. La differenza può essere piccola se applicata correttamente per un problema sufficientemente complesso, ma è comunque più costosa. Sono possibili eccezioni come l'ottimizzazione della ricorsione della coda.
Bernhard Barker

Non sono sicuro di un singolo ciclo for in ogni caso. Considera una ricorsione più complessa o una ricorsione con più di una singola variabile
SomeWittyUsername

@dasblinkenlight Potrebbe essere teoricamente possibile ridurre più loop a uno solo, ma non ne sono sicuro.
SomeWittyUsername

@icepack Sì, è possibile. Potrebbe non essere carino, ma è possibile.
Bernhard Barker

Non sono sicuro di essere d'accordo con la tua prima affermazione. Le CPU stesse non modellano affatto la ricorsione, sono le istruzioni in esecuzione sulla CPU che modellano la ricorsione. in secondo luogo, una struttura ad anello non ha (necessariamente) un set di dati in crescita e in diminuzione dinamicamente, dove un algoritmo ricorsivo di solito farà in modo che per ogni livello profondo la ricorsione debba passare.
trumpetlicks

28

Domanda:

E se la ricorsione è solitamente più lenta, qual è la ragione tecnica per utilizzarla per l'iterazione del ciclo?

Risposta :

Perché in alcuni algoritmi è difficile risolverlo iterativamente. Prova a risolvere la ricerca approfondita sia in modo ricorsivo che iterativo. Avrai l'idea che è semplicemente difficile risolvere DFS con l'iterazione.

Un'altra buona cosa da provare: prova a scrivere Merge sort in modo iterativo. Ti ci vorrà un bel po 'di tempo.

Domanda:

È corretto affermare che ovunque sia usata la ricorsione si potrebbe usare un ciclo for?

Risposta :

Sì. Questo thread ha un'ottima risposta per questo.

Domanda:

E se è sempre possibile convertire una ricorsione in un ciclo for, esiste una regola pratica per farlo?

Risposta :

Fidati di me. Prova a scrivere la tua versione per risolvere in modo iterativo la ricerca approfondita. Noterai che alcuni problemi sono più facili da risolvere in modo ricorsivo.

Suggerimento: la ricorsione è utile quando si risolve un problema che può essere risolto con la tecnica del divide et impera .


3
Apprezzo il tentativo di fornire una risposta autorevole e sono sicuro che l'autore sia intelligente ma "fidati di me" non è una risposta utile a una domanda significativa la cui risposta non è immediatamente ovvia. Esistono algoritmi molto semplici per eseguire una ricerca iterativa in profondità. Vedere l'esempio in fondo a questa pagina per una descrizione di un algoritmo in pseudocodice: csl.mtu.edu/cs2321/www/newLectures/26_Depth_First_Search.html
jdelman

3

Oltre ad essere più lenta, la ricorsione può anche causare errori di overflow dello stack a seconda di quanto è profonda.


3

Per scrivere un metodo equivalente usando l'iterazione, dobbiamo usare esplicitamente uno stack. Il fatto che la versione iterativa richieda uno stack per la sua soluzione indica che il problema è abbastanza difficile da poter trarre vantaggio dalla ricorsione. Come regola generale, la ricorsione è più adatta per problemi che non possono essere risolti con una quantità fissa di memoria e di conseguenza richiedono uno stack quando vengono risolti iterativamente. Detto questo, la ricorsione e l'iterazione possono mostrare lo stesso risultato mentre seguono schemi diversi. Per decidere quale metodo funziona meglio è caso per caso e la migliore pratica è scegliere in base allo schema seguito dal problema.

Ad esempio, per trovare l'ennesimo numero triangolare della sequenza triangolare: 1 3 6 10 15… Un programma che utilizza un algoritmo iterativo per trovare l'ennesimo numero triangolare:

Utilizzando un algoritmo iterativo:

//Triangular.java
import java.util.*;
class Triangular {
   public static int iterativeTriangular(int n) {
      int sum = 0;
      for (int i = 1; i <= n; i ++)
         sum += i;
      return sum;
   }
   public static void main(String args[]) {
      Scanner stdin = new Scanner(System.in);
      System.out.print("Please enter a number: ");
      int n = stdin.nextInt();
      System.out.println("The " + n + "-th triangular number is: " + 
                            iterativeTriangular(n));
   }
}//enter code here

Utilizzando un algoritmo ricorsivo:

//Triangular.java
import java.util.*;
class Triangular {
   public static int recursiveTriangular(int n) {
      if (n == 1)
     return 1;  
      return recursiveTriangular(n-1) + n; 
   }

   public static void main(String args[]) {
      Scanner stdin = new Scanner(System.in);
      System.out.print("Please enter a number: ");
      int n = stdin.nextInt();
      System.out.println("The " + n + "-th triangular number is: " + 
                             recursiveTriangular(n)); 
   }
}

1

La maggior parte delle risposte sembra presumere che iterative= for loop. Se il tuo ciclo for non è limitato ( a la C, puoi fare quello che vuoi con il tuo contatore di loop), allora è corretto. Se si tratta di un vero e proprio for ciclo (come dire in Python o la maggior parte dei linguaggi funzionali in cui non è possibile modificare manualmente il contatore del ciclo), allora è non corretto.

Tutte le funzioni (calcolabili) possono essere implementate sia in modo ricorsivo che utilizzando whilecicli (o salti condizionali, che sono fondamentalmente la stessa cosa). Se ti limiti veramente afor loops , otterrai solo un sottoinsieme di quelle funzioni (quelle ricorsive primitive, se le tue operazioni elementari sono ragionevoli). Certo, è un sottoinsieme piuttosto ampio che contiene ogni singola funzione che potresti incontrare nella pratica.

Ciò che è molto più importante è che molte funzioni sono molto facili da implementare in modo ricorsivo e terribilmente difficili da implementare iterativamente (la gestione manuale dello stack di chiamate non conta).


1

Sì, come detto da Thanakron Tandavas ,

La ricorsione è utile quando stai risolvendo un problema che può essere risolto con la tecnica del divide et impera.

Ad esempio: Towers of Hanoi

  1. N anelli di dimensioni crescenti
  2. 3 poli
  3. Gli anelli iniziano impilati sul palo 1. L'obiettivo è spostare gli anelli in modo che siano impilati sul palo 3 ... Ma
    • Può spostare solo un anello alla volta.
    • Non è possibile mettere un anello più grande sopra uno più piccolo.
  4. La soluzione iterativa è "potente ma brutta"; la soluzione ricorsiva è "elegante".

Un esempio interessante. Immagino che tu conosca il documento di MC Er "Le torri di Hanoi e i numeri binari". Trattata anche in un fantastico video di 3brown1blue.
Andrestand

0

Mi sembra di ricordare che il mio professore di informatica ha detto in passato che tutti i problemi che hanno soluzioni ricorsive hanno anche soluzioni iterative. Dice che una soluzione ricorsiva è solitamente più lenta, ma vengono spesso utilizzate quando sono più facili da ragionare e codificare rispetto alle soluzioni iterative.

Tuttavia, nel caso di soluzioni ricorsive più avanzate, non credo che sarà sempre in grado di implementarle utilizzando un semplice forciclo.


È sempre possibile convertire un algoritmo ricorsivo in uno iterativo (utilizzando gli stack). Potresti non ritrovarti con un ciclo particolarmente semplice, ma è possibile.
Bernhard Barker

-4

ricorsione + memorizzazione potrebbe portare a una soluzione più efficiente rispetto a un approccio iterativo puro, ad esempio controllare questo: http://jsperf.com/fibonacci-memoized-vs-iterative-for-large-n


3
Qualsiasi codice ricorsivo può essere convertito in codice iterativo funzionalmente identico utilizzando gli stack. La differenza che stai mostrando è la differenza tra due approcci per risolvere lo stesso problema, non la differenza tra ricorsione e iterazione.
Bernhard Barker

-6

Risposta breve: il compromesso è che la ricorsione è più veloce e i cicli occupano meno memoria in quasi tutti i casi. Tuttavia di solito ci sono modi per cambiare il ciclo for o la ricorsione per renderlo più veloce

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.