Ricorsione - è "dividi e conquista" o "riutilizzo del codice"


11

La ricorsione - come tutti sappiamo - è uno di quei problemi - che avvolgere la testa sembra come raggiungere una "pietra miliare" nel tuo viaggio di programmazione.

Ma quando si tratta di usarlo effettivamente nei problemi del mondo reale - conoscere la meccanica della ricorsione NON è abbastanza - bisogna anche capire la natura dei problemi in cui la ricorsione è la soluzione più adatta.

Quindi la mia domanda è questa ...

  • quali sono i "modelli di problema" che richiedono la soluzione della ricorsione?
  • la ricorsione è una forma di strategia di "divisione e conquista" o una forma di "riutilizzo del codice" - oppure è un modello di progettazione a sé stante
  • puoi darci un esempio di un problema del mondo reale in cui la ricorsione viene in mente come una soluzione immediata

-- AGGIORNARE --

molte risposte si riferiscono a "problemi reali" come attraversamento di alberi, fattoriali, ecc. Preferirei "i veri problemi reali" - lascia che ti dia un esempio ...

Avevamo un GRANDE blocco di testo (circa 30 MB di testo come un elenco collegato di structs) e dovevamo crearne un indice per la ricerca di testo completo. Dovevamo mantenere l'intero indice in memoria e reindicizzare il testo ogni 10 minuti.

Ogni 10 minuti confronteremmo l'intero testo (due elenchi collegati, riga per riga) con un pezzo di testo appena generato - per vedere quale riga è stata modificata - e quindi indicizzeremmo solo quella riga - in quel modo potremmo evitare di dover reindicizzare il testo INTERO. Ricorda: dovevamo trovare i punti diff tra due elenchi collegati da 30 MB.

Uno dei miei colleghi ha escogitato un fantastico programma che ha usato la ricorsione HEAVY per confrontare le linee - e quindi raccogliere le posizioni in cui i mandrini differivano in un array - sì, lo so che sembra sconcertante - come potrebbe essere utile la ricorsione qui - ma lo ha fatto.

Il punto è: come ha potuto vedere che questo problema potrebbe essere risolto in modo intelligente con un uso intenso della ricorsione?


30 MB sono davvero grandi in questi giorni in cui la maggior parte dei computer ha GB di RAM e TB di spazio sul disco rigido?
JB King,

30 MB potrebbero NON essere grandi, ma considerando il tipo di struttura di dati in cui è stato inserito il nostro testo, è stato davvero GRANDE frammento di testo da PROCESSARE e DIFF.
treecoder,

3
"Attraversare una struttura di cartelle" non è abbastanza REALE? E non riesco completamente a vedere, nel tuo esempio, come la ricorsione non dovrebbe essere intuitiva qui e perché il suo uso dovrebbe essere particolarmente notevole. Il tuo collega ha progettato un algoritmo ricorsivo, proprio come qualsiasi altro algoritmo. Potresti anche chiedere come Hoare abbia avuto l'idea di risolvere il problema di smistamento in modo ricorsivo.
Konrad Rudolph,

2
Ho ragione nel pensare che intendevi "riutilizzo del codice" più che "eseguire la stessa serie di operazioni un numero indeterminato di volte"? Ciò è contrario al "riutilizzo del codice", nel senso di scrivere codice generico per l'uso altrove.
Andy Hunt,

4
Ma l'attraversamento degli alberi è un "vero problema reale", che molte persone incontrano quasi quotidianamente.
Falcon,

Risposte:


16
  • quali sono i "modelli di problema" che richiedono la soluzione della ricorsione?

Non direi che esiste un modello di problema per l'uso della ricorsione. Ogni funzione che può essere implementata con ricorsione può anche essere implementata in modo iterativo, spesso spingendo e facendo scoppiare una pila.

È una questione di espressione e anche di prestazione. Gli algoritmi iterativi spesso hanno prestazioni migliori e sono più facili da ottimizzare. Tuttavia, gli algoritmi ricorsivi beneficiano di un'espressione più chiara e quindi sono spesso più facili da leggere, comprendere e implementare.

Alcune cose non possono nemmeno essere espresse senza ricorsione, ad esempio liste infinite. I cosiddetti linguaggi funzionali dipendono fortemente dalla ricorsione, in quanto è il loro modo di esprimersi naturale. Il detto è: "La programmazione ricorsiva è una programmazione funzionale eseguita correttamente".

  • la ricorsione è una forma di strategia di "divisione e conquista" o una forma di "riutilizzo del codice" - oppure è un modello di progettazione a sé stante

Non lo definirei un modello di progettazione. È una questione di espressione. A volte un'espressione ricorsiva è semplicemente più potente e più espressiva e porta quindi a un codice migliore e più pulito.

  • puoi darci un esempio di un problema del mondo reale in cui la ricorsione viene in mente come una soluzione immediata

Tutto ciò che deve attraversare gli alberi sarà adeguatamente espresso da un algoritmo ricorsivo.


7
"Ogni funzione che può essere implementata con la ricorsione può anche essere implementata in modo iterativo, spesso spingendo e facendo scoppiare una pila." Dopotutto, nelle lingue che utilizzano la memoria basata su stack, si sta già spingendo e facendo scattare i dati delle funzioni dentro e fuori dallo stack quando si utilizza la ricorsione.
JAB,

Solo se compili o interpreti il ​​linguaggio da una macchina ;-) Inoltre, da un punto di vista molto elevato, l'espressione e la lingua sono completamente indipendenti dalla macchina, dall'hardware e dal sistema operativo, quindi non c'è necessariamente uno stack.
Falcon,

Ah, sì, hai assolutamente ragione. Avrei dovuto dire "nelle implementazioni di compilatori di lingue / linguaggio che utilizzano memoria basata su stack".
JAB,

In generale, anche tu hai ragione. Non volevo apparire pignolo.
Falcon,

2
Le liste infinite possono essere espresse senza ricorsione, almeno senza un'implementazione ricorsiva. I generatori Python possono farlo, così come i generatori in Icon da cui Python sembra aver preso in prestito l'idea. Credo che F # possa fare questo trucco, anche se non ne sono sicuro. Fondamentalmente, i generatori sono un caso speciale di co-routine (come il multitasking cooperativo) che ben si adattano all'implementazione di liste pigre. Ogni volta che un generatore "produce" un risultato, il chiamante riprende il controllo e il generatore rimane inattivo fino a quando non viene richiesto il risultato successivo.
Steve314,

8

la ricorsione è una forma di strategia di "divisione e conquista" o una forma di "riutilizzo del codice" - oppure è un modello di progettazione a sé stante

Nessuno dei due. Dividi e conquista usa la ricorsione. Ma la ricorsione non è necessariamente una divisione e conquista poiché quest'ultima significa dividere un problema in due (o più) parti e risolverle simmetricamente. In ricorsione, non lo fai.

Il riutilizzo del codice non è completamente correlato e un modello di progettazione entra in gioco a un livello molto più elevato. Ad esempio, persino divide & conquistare (anche un modello di livello superiore rispetto alla ricorsione) non è ancora considerato un modello di progettazione - piuttosto, è un modello algoritmico .

puoi darci un esempio di un problema del mondo reale in cui la ricorsione viene in mente come una soluzione immediata

Traversata di alberi. O più generalmente attraversamento grafico. Ciò include in particolare l'attraversamento di una struttura di cartelle.

E ovviamente tutto ciò che usa divide & conquistare o programmazione dinamica poiché entrambi sono espressi naturalmente in termini di ricorsione.


La programmazione dinamica non è sempre espressa naturalmente come ricorsione. In effetti, la programmazione dinamica si riferisce rigorosamente agli approcci tabulari - esclusa la memoizzazione. Le opinioni sembrano variare su questo, ma la "programmazione" in "programmazione dinamica" è in realtà un termine matematico, riferito ad approcci tabulari (un pezzo di curiosità che ho raccolto dal corso sugli algoritmi opensourceware del MIT). Quindi, in modo rigoroso, la programmazione dinamica sfrutta una sottostruttura ottimale usando ciò che spesso viene espresso più facilmente come un semplice ciclo. La memorizzazione è molto più probabile che implichi la ricorsione, ma non necessariamente.
Steve314,

1
@ Steve314 Concordo sul fatto che l'implementazione pratica di DP (sia essa in un programma per computer o manualmente) utilizza raramente la ricorsione. Ma l' idea è intrinsecamente basata su una relazione di ricorrenza - che è solo una formula ricorsiva! - e una custodia di base.
Konrad Rudolph,

Concordo sul fatto che la "sottostruttura ottimale" (una soluzione ottimale ha soluzioni parziali ottimali) è un'idea ricorsiva. Questa è una visione matematica / informatica della ricorsione, che non riguarda direttamente l'implementazione - ma il ruolo della ricorsione nell'informatica è un punto importante da sottolineare. Pochi algoritmi (e probabilmente nessun modello di progettazione) sono strumenti importanti nell'informatica - la maggior parte sono argomenti puramente da studiare piuttosto che strumenti da utilizzare nello studio di qualcos'altro.
Steve314,

4
what are the "problem patterns" that call for the solution of recursion

Derivato dall'auto-somiglianza dei frattali, direi che l'uguaglianza di sé o l'identità di sé (o comunque si chiama) è un tipico modello di problema per la ricorsione. Vale a dire un problema può essere suddiviso in sotto-problemi che hanno la stessa struttura del problema principale.

Nell'attraversamento degli alberi menzionato, ogni sottoalbero è un albero pieno in sé, proprio come l'albero principale, e l'albero principale può essere un sottoalbero all'interno di un altro albero.

Quindi immagino che il tuo collega abbia scoperto le proprietà di uguaglianza del tuo problema di indicizzazione. Oppure ha fatto il contrario e ha trasformato il problema in una rappresentazione eguale.


1
+1 per "un problema può essere suddiviso in sotto-problemi che hanno la stessa struttura del problema principale"
treecoder

+1 e parafrasi: dove la soluzione del problema si applica ai livelli figlio. Il mio esempio nel mondo reale è trovare addebiti su carta di credito che contribuiscono a un "batch". Il software di contabilità avrà gli addebiti individuali e il deposito in batch nel conto corrente. Il mio caso potrebbe diventare una domanda qui poiché StackOverflow non era troppo acuto al riguardo. stackoverflow.com/questions/14719806
Chris K,

3

Bene, la ricorsione può essere facilmente compresa se si tenta di trasformare i loop imperativi in ​​funzioni funzionali. Comunque, proviamo a dare risposte a tutte le domande:

quali sono i "modelli di problema" che richiedono la soluzione della ricorsione?

Se hai una struttura ad albero o un algoritmo avrai bisogno di ricorsione. Se il tuo codice imperativo si occupa di uno stack, avrai bisogno di ricorsione. Se un determinato passaggio nel tuo algoritmo dipende dai passaggi precedenti (think loop), devi ricorrere. Bisogno qui deve essere interpretato come può usare.

la ricorsione è una forma di strategia di "divisione e conquista" o una forma di "riutilizzo del codice" - oppure è un modello di progettazione a sé stante

Nessuna. Dividi e conquista usa la ricorsione, ma può essere implementato con stack. Il riutilizzo del codice si riferisce a qualcos'altro. I modelli di progettazione sono più complicati della semplice ricorsione.

puoi darci un esempio di un problema del mondo reale in cui la ricorsione viene in mente come una soluzione immediata

Analisi e tutto ciò che riguarda le strutture ad albero. Persino strutture ad albero implicite.


3

Se esiste un modo per semplificarlo in modo che sia facile, questo può essere l'indizio che la ricorsione potrebbe funzionare. È possibile prendere l' ordinamento e la ricerca di esempi in cui esistono rispettivamente soluzioni ricorsive come Merge Sort e Binary Search .

Qualcosa da tenere a mente è come alcuni problemi possano essere risolti male con la ricorsione come un fattoriale.

Per quanto riguarda un esempio del mondo reale in cui utilizzerei la ricorsione, cercare un libro da uno scaffale può essere fatto abbastanza facilmente in modo ricorsivo. Guardo solo il libro e, se non è quello che voglio, passo al prossimo. Mi fermo quando trovo il libro o raggiungo la fine della riga. Il ciclo su come controllare un libro e passare a quello successivo potrebbe essere fatto in modo ricorsivo. Forse questo è troppo reale di un esempio.


2

quali sono i "modelli di problema" che richiedono la soluzione della ricorsione?

In termini molto generali, la ricorsione è richiesta quando si risolve un problema in cui f (x) = f (g (x)) . A meno che tu non stia bene con la ricorsione infinita, g (x) non dovrebbe valutare in x .

la ricorsione è una forma di strategia di "divisione e conquista" o una forma di "riutilizzo del codice" - oppure è un modello di progettazione a sé stante

Nessuno dei precedenti. È solo un modo per fare la stessa cosa ripetutamente, a volte in base alle variazioni dell'input. Il concetto è stato molto più lungo dei modelli di progettazione, del riutilizzo del codice o persino dei computer, del resto.

puoi darci un esempio di un problema del mondo reale in cui la ricorsione viene in mente come una soluzione immediata

Fattori, sequenza di Fibonacci, attraversamento di alberi e molti altri problemi possono essere risolti con la ricusione. La ricorsione nel senso di una funzione che chiama se stessa non è necessariamente il modo migliore per implementare quel tipo di cose; ci sono altri modi per ottenere lo stesso effetto (ad esempio, uno stack e un loop) che potrebbe essere più desiderabile.


-1

Quando scrivi un algoritmo ricorsivo, di solito traduci una definizione ricorsiva del problema nel codice. Quindi non è nemmeno necessario sapere come verrà eseguito.

È ciò che accade nella programmazione funzionale. In effetti, si specifica cosa (definizione) anziché come . In altre parole, si definisce la base e quindi si definisce il problema nel termine di un sotto-problema.

Ad esempio, considera l' Factorialalgoritmo

  • Definisci la base: Factorial (1) = 1;
  • Definisci Fattoriale n: Fattoriale (n) = n * Fattoriale (n-1);

Quindi, quando si verifica un problema, si dovrebbe pensare se è possibile definirlo in modo ricorsivo o meno, se è possibile definirlo in modo ricorsivo, è quasi risolto.

Tuttavia, qualsiasi funzione ricorsiva non dovrebbe essere una definizione ricorsiva. È possibile definire la base e correlare (definire) la soluzione del problema principale alla soluzione (definizione) dei sotto-problemi. Ma per questa relazione potresti aver bisogno di una procedura.

Un esempio è MergeSortin cui mergepotrebbe essere una procedura imperativa per mettere in relazione la definizione o la soluzione dell'ordinamento dell'intero array con il tipo di sotto-array.

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.