Quali sono le considerazioni per determinare se è possibile utilizzare la ricorsione per risolvere un problema?


10

A volte nelle interviste, posso usare la ricorsione per risolvere un problema (come aggiungere 1un numero intero di precisione infinita) o quando il problema si presenta adatto all'uso della ricorsione. A volte, potrebbe essere dovuto semplicemente all'uso della ricorsione per la risoluzione dei problemi, quindi senza pensare molto, la ricorsione viene utilizzata per risolvere il problema.

Tuttavia, quali sono le considerazioni prima di poter decidere che è opportuno utilizzare la ricorsione per risolvere un problema?


Alcuni pensieri che ho avuto:

Se utilizziamo la ricorsione su dati che vengono dimezzati ogni volta, sembra che non sia un problema utilizzare la ricorsione, poiché tutti i dati che possono adattarsi a 16 GB di RAM, o anche un disco rigido da 8 TB, possono essere gestiti da ricorsione a soli 42 livelli di profondità. (quindi nessun overflow dello stack (penso in alcuni ambienti, lo stack può avere una profondità di 4000 livelli, molto più di 42, ma allo stesso tempo, dipende anche da quante variabili locali hai, come ogni stack di chiamate, occupano più memoria se ci sono molte variabili locali, ed è la dimensione della memoria, non il livello, che determina l'overflow dello stack)).

Se calcoli i numeri di Fibonacci usando la pura ricorsione, devi davvero preoccuparti della complessità temporale, a meno che non memorizzi nella cache i risultati intermedi.

E che ne dici di aggiungere 1un numero intero di precisione infinita? Forse è discutibile, come, lavorerai con numeri che sono lunghi 3000 cifre o 4000 cifre, così grandi che può causare un overflow dello stack? Non ci ho pensato, ma forse la risposta è no, non dovremmo usare la ricorsione, ma basta usare un semplice ciclo, perché cosa succede se in qualche applicazione, il numero deve davvero essere lungo 4000 cifre, per controllare alcuni proprietà del numero, ad esempio se il numero è primo o no.

L'ultima domanda è: quali sono le considerazioni prima di poter decidere di utilizzare la ricorsione per risolvere un problema?


7
In realtà è piuttosto semplice: "La soluzione è banale se posso supporre che la soluzione a un problema leggermente più piccolo sia nota?"
Kilian Foth,

ma che dire del numero di Fibonacci o dell'aggiunta 1all'intero di precisione infinita? Si può dire, sì, riducono a problemi più piccoli, ma la pura ricorsione non è adatta a questo
polarità

Potete trovare questo utile - stackoverflow.com/questions/3021/...
Kishor Kundan

Risposte:


15

Una considerazione è se il tuo algoritmo deve essere una soluzione astratta o una pratica soluzione eseguibile. Nel primo caso, gli attributi che stai cercando sono correttezza e facilità di comprensione per il tuo pubblico target 1 . In quest'ultimo caso, anche le prestazioni sono un problema. Queste considerazioni possono influenzare la tua scelta.

Una seconda considerazione (per una soluzione pratica) è se il linguaggio di programmazione (o più rigorosamente, la sua implementazione) che stai usando fa l'eliminazione della coda? Senza l'eliminazione della coda, la ricorsione è più lenta dell'iterazione e la ricorsione profonda può causare problemi di overflow dello stack.

Si noti che una soluzione (corretta) ricorsiva può essere trasformata in una soluzione equivalente non ricorsiva, quindi non è necessario effettuare una scelta difficile tra i due approcci.

Infine, a volte la scelta tra formulazioni ricorsive e non ricorsive è motivata dalla necessità di dimostrare (in senso formale) le proprietà di un algoritmo. Le formulazioni ricorsive consentono più direttamente la prova per induzione.


1 - Ciò include considerazioni come se il pubblico di destinazione ... e questo potrebbe includere i programmatori che leggono il codice pratico ... vedrebbero uno stile di soluzione "più naturale" dell'altro. La nozione di "naturale" varierà da persona a persona, a seconda di come hanno appreso la programmazione o gli algoritmi. (Sfido chiunque proponga la "naturalezza" come criterio primario per decidere di utilizzare la ricorsione (o meno) per definire la "naturalezza" in termini oggettivi, ovvero come la misureresti.)


2
Alcuni problemi sono semplicemente più naturalmente espressi usando la ricorsione. Traversal tree, per esempio.
Frank Hileman,

Aggiornato la mia risposta per rispondere a quel punto,
Stephen C

1
Per quanto riguarda la "naturalezza": l'attraversamento di alberi senza ricorsione, ad esempio, tende a produrre un codice più ampio e meno generico. Si consideri ad esempio l'uso di chiamate polimorfiche per attraversare l'albero, con un comportamento diverso per i nodi foglia e composito. Questo non è possibile senza ricorsione.
Frank Hileman,

1) Hai accettato la mia sfida per definire "naturale" ancora? 2) Poiché è possibile simulare la ricorsione utilizzando una struttura di dati dello stack, è possibile implementare anche l'attraversamento degli alberi in quel modo. Potrebbe non essere il modo più efficiente ... e non ti darà il codice più leggibile ... ma è sicuramente possibile e pratico farlo.
Stephen C

A proposito, il primo linguaggio di programmazione che ho imparato (FORTRAN 4) non ha supportato affatto la ricorsione.
Stephen C

1

Come programmatore C / C ++, la mia massima considerazione è la prestazione. Il mio processo decisionale è qualcosa del tipo:

  1. Qual è la profondità massima dello stack di chiamate? Se troppo profondo, sbarazzarsi della ricorsione. Se superficiale, vai a 2.

  2. È probabile che questa funzione costituisca un collo di bottiglia nel mio programma? Se sì, vai a 3. In caso negativo, mantieni la ricorsione. Se non sei sicuro, esegui un profiler.

  3. Qual è la frazione del tempo della CPU impiegato per le chiamate di funzione ricorsive? Se le chiamate di funzione richiedono molto meno tempo rispetto al resto del corpo della funzione, è possibile utilizzare la ricorsione.


0

Tuttavia, quali sono le considerazioni prima di poter decidere che è opportuno utilizzare la ricorsione per risolvere un problema?

Quando scrivo funzioni in Scheme, trovo naturale scrivere funzioni ricorsive di coda senza pensare troppo.

Quando scrivo funzioni in C ++, mi trovo a discutere prima di usare una funzione ricorsiva. Le domande che mi pongo sono:

  • Il calcolo può essere eseguito utilizzando un algoritmo iterativo? Se sì, usa un approccio iterativo.

  • La profondità della ricorsione può aumentare in base alle dimensioni del modello? Di recente mi sono imbattuto in un caso in cui la profondità della ricorsione è cresciuta fino a quasi 13000 a causa delle dimensioni del modello. Ho dovuto convertire la funzione per utilizzare un algoritmo iterativo post-fretta.

    Per questo motivo, non consiglierei di scrivere un algoritmo di attraversamento di alberi usando funzioni ricorsive. Non si sa mai quando l'albero diventa troppo profondo per l'ambiente di runtime.

  • La funzione può diventare troppo contorta usando un algoritmo iterativo? In caso affermativo, utilizzare una funzione ricorsiva. Non ho provato a scrivere qsortusando un approccio iterativo ma ho la sensazione che usare una funzione ricorsiva sia più naturale per questo.


0

Per i numeri di Fibonacci, l'ingenua "ricorsione" è semplicemente stupida. Questo perché porta alla risoluzione ripetuta dello stesso sottoproblema.

Esiste in realtà una banale variazione dei numeri di Fibonacci in cui la ricorsione è molto efficiente: dato un numero n ≥ 1, calcola sia fib (n) che fib (n-1). Quindi hai bisogno di una funzione che restituisca due risultati, chiamiamo questa funzione fib2.

L'implementazione è abbastanza semplice:

function fib2 (n) -> (fibn, fibnm1) {
    if n ≤ 1 { return (1, 1) }
    let (fibn, fibnm1) = fib2 (n-1)
    return (fibn + fibnm1, fibn)
}

pensi di poter scrivere il programma in una lingua comune? e il tuo fib2restituisce una coppia di numeri, e il tuo fib2()non si adatta all'interfaccia di fib(), che è, dato un numero, restituisce un numero. Sembra che tu fib(n)debba tornare, fib2(n)[0]ma per favore sii specifico
polarità
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.