Esempi reali di ricorsione [chiuso]


95

Quali sono i problemi del mondo reale in cui un approccio ricorsivo è la soluzione naturale oltre alla ricerca approfondita (DFS)?

(Non considero la Torre di Hanoi , il numero di Fibonacci oi problemi fattoriali del mondo reale. Sono un po 'artificiosi nella mia mente.)


2
Grazie per tutti i suggerimenti, ma tutti suggeriscono attraversamenti di albero / rete. Le tesi sono essenzialmente tutti esempi di Depth-First-Search (o BFS immagino). Stavo cercando altri algoritmi / problemi ben motivati.
redfood

10
Mi piace questa domanda! "Dimmi tutti gli usi della tecnica X, TRANNE il principale uso pratico della tecnica X"
Justin Standard

1
Uso sempre la ricorsione, ma di solito per cose di matematica e grafica. Sto cercando di cercare esempi di ricorsione che sarebbero significativi per i non programmatori.
redfood

6
Scegli i tuoi romanzi d'avventura! Voglio leggere l'intera cosa e la ricorsione è il modo migliore per farlo.
Andres

Non c'è ricorsione nel mondo reale. La ricorsione è un'astrazione matematica. Puoi modellare molte cose usando la ricorsione. In questo senso, Fibonacci è assolutamente reale, poiché ci sono alcuni problemi del mondo reale che possono essere modellati in questo modo. Se pensi che Fibonacci non sia il mondo reale, direi che anche tutti gli altri esempi sono astrazioni, non esempi del mondo reale.
Zane

Risposte:


41

Ci sono molti esempi di mathy qui, ma volevi un mondo reale esempio del , quindi con un po 'di riflessione, questo è forse il meglio che posso offrire:

Trovi una persona che ha contratto una determinata infezione contagiosa, che non è fatale, e si risolve rapidamente (Tipo A), ad eccezione di una persona su 5 (le chiameremo tipo B) che ne viene infettata permanentemente e mostra no sintomi e agisce semplicemente come un diffusore.

Questo crea ondate di caos abbastanza fastidiose ogni volta che il tipo B infetta una moltitudine di tipo A.

Il tuo compito è rintracciare tutti i tipi B e immunizzarli per fermare la spina dorsale della malattia. Purtroppo, però, non puoi somministrare una cura a livello nazionale a tutti, perché le persone che sono di tipo A sono anche mortalmente allergiche alla cura che funziona per il tipo B.

Il modo in cui lo faresti sarebbe una scoperta sociale, dato che una persona infetta (Tipo A), sceglierà tutti i suoi contatti nell'ultima settimana, contrassegnando ogni contatto su un mucchio. Quando si verifica che una persona è infetta, aggiungerla alla coda "follow up". Quando una persona è di tipo B, aggiungila al "follow up" in testa (perché vuoi fermarlo velocemente).

Dopo aver elaborato una determinata persona, selezionare la persona dalla parte anteriore della coda e applicare l'immunizzazione se necessario. Ottieni tutti i loro contatti precedentemente non visitati, quindi verifica se sono infetti.

Ripeti fino a quando la coda delle persone infette diventa 0, quindi attendi un altro focolaio.

(Ok, questo è un po 'iterativo, ma è un modo iterativo di risolvere un problema ricorsivo, in questo caso, l'ampiezza del primo attraversamento di una base di popolazione che cerca di scoprire probabili percorsi verso i problemi, e inoltre, le soluzioni iterative sono spesso più veloci ed efficaci , e rimuovo compulsivamente la ricorsione ovunque così tanto che è diventato istintivo .... dannazione!)


2
Grazie - questo è ancora un attraversamento del grafico ma è ben motivato e ha senso per i non programmatori.
redfood

Credo che trovare il paziente 0 sarebbe un esempio migliore. Determina tutte le interazioni che potrebbero aver causato l'infezione. Ripeti su tutti i soggetti coinvolti che erano contagiosi al momento dell'interazione fino a quando non vengono trovati contagiosi
William FitzPatrick

4
questo esempio del mondo reale sembra così familiare ora :(
haroldolivieri il

109

Un esempio reale di ricorsione

Un girasole


12
codificato con ricorsione dall'architetto Matrix :)
Marcel

3
Come va questo ricorsivo? Certo, è carino. Ma ricorsivo? Un cavolo frattale avrebbe funzionato bene, ma non vedo auto-somiglianze in questo fiore.
Clément

1
Beh, è ​​un po 'sfacciato, ma è un esempio di fillotassi, che può essere descritta con la sequenza di Fibonacci, che è comunemente implementata attraverso la ricorsione.
Hans Sjunnesson

1
"comunemente implementato attraverso la ricorsione" non significa necessariamente che il fiore lo faccia. Forse sì; Non sono abbastanza un biologo molecolare per saperlo, ma senza una spiegazione su come funziona, non lo vedo particolarmente utile. Downvoting. Se vuoi aggiungere una descrizione (che sia biologicamente accurata o meno, potrebbe fornire informazioni) per accompagnarla, riconsidererò felicemente il voto.
lindes

65

Che ne dici di qualsiasi cosa che coinvolga una struttura di directory nel file system. Ricerca ricorsiva di file, eliminazione di file, creazione di directory, ecc.

Ecco un'implementazione Java che stampa ricorsivamente il contenuto di una directory e delle sue sottodirectory.

import java.io.File;

public class DirectoryContentAnalyserOne implements DirectoryContentAnalyser {

    private static StringBuilder indentation = new StringBuilder();

    public static void main (String args [] ){
        // Here you pass the path to the directory to be scanned
        getDirectoryContent("C:\\DirOne\\DirTwo\\AndSoOn");
    }

    private static void getDirectoryContent(String filePath) {

        File currentDirOrFile = new File(filePath);

        if ( !currentDirOrFile.exists() ){
            return;
        }
        else if ( currentDirOrFile.isFile() ){
            System.out.println(indentation + currentDirOrFile.getName());
            return;
        }
        else{
            System.out.println("\n" + indentation + "|_" +currentDirOrFile.getName());
            indentation.append("   ");

            for ( String currentFileOrDirName : currentDirOrFile.list()){
                getPrivateDirectoryContent(currentDirOrFile + "\\" + currentFileOrDirName);
            }

            if (indentation.length() - 3 > 3 ){
                indentation.delete(indentation.length() - 3, indentation.length());
            }
        }       
    }

}

2
Un file system fornisce la motivazione (il che è positivo, grazie) ma questo è un esempio specifico di DFS.
redfood

4
Non ho ricevuto l'acronimo "DFS" - è passato un po 'di tempo da quando mi sono seduto in una classe.
Matt Dillard

5
ricerca approfondita: dfs (nodo) {foreach child in node {visit (child); }}
Haoest

Per il semplice esempio di codice, si veda ad esempio stackoverflow.com/questions/126756/...
Jonik

C'è un errore in questo codice? GetPrivateDirectoryContent () non dovrebbe essere sostituito con getDirectoryContent ()?
Shn_Android_Dev


16

L'esempio di Matt Dillard è buono. Più in generale, qualsiasi camminata su un albero può essere generalmente gestita con la ricorsione molto facilmente. Ad esempio, compilare alberi di analisi, camminare su XML o HTML, ecc.


Trovo che questa "compilazione di alberi di analisi" sia una risposta sensata, che coinvolge alberi ma non è ancora un problema di ricerca, come voleva il richiedente. Potrebbe essere generalizzato a qualche nozione generale di compilazione o interpretazione di una lingua. Potrebbe anche essere "interpretare" (capire, ascoltare) una lingua naturale, ad esempio l'inglese.
imz - Ivan Zakharyaschev


13

La ricorsione è appropriata ogni volta che un problema può essere risolto dividendolo in sotto-problemi, che possono utilizzare lo stesso algoritmo per risolverli. Gli algoritmi sugli alberi e gli elenchi ordinati sono una scelta naturale. Molti problemi nella geometria computazionale (e nei giochi 3D) possono essere risolti in modo ricorsivo utilizzando alberi BSP ( binary space partitioning ), suddivisioni grasse o altri modi per dividere il mondo in sotto-parti.

La ricorsione è appropriata anche quando si cerca di garantire la correttezza di un algoritmo. Data una funzione che accetta input immutabili e restituisce un risultato che è una combinazione di chiamate ricorsive e non ricorsive sugli input, di solito è facile dimostrare che la funzione è corretta (o meno) usando l'induzione matematica. È spesso intrattabile farlo con una funzione iterativa o con input che possono mutare. Questo può essere utile quando si ha a che fare con calcoli finanziari e altre applicazioni in cui la correttezza è molto importante.


11

Sicuramente molti compilatori là fuori usano pesantemente la ricorsione. I linguaggi dei computer sono intrinsecamente ricorsivi stessi (cioè, puoi incorporare istruzioni "if" all'interno di altre istruzioni "if", ecc.).


Le istruzioni if ​​incorporate non sono ricorsive.
John Meagher

Ma analizzarli richiede la ricorsione, John.
Apocalisp

2
John, il fatto che tu possa annidare le istruzioni if ​​significa che la definizione del linguaggio (e probabilmente il parser del linguaggio) è ricorsiva.
Derek Park,

La discesa ricorsiva è uno dei modi più semplici per codificare manualmente un compilatore. Non è facile come usare uno strumento come yacc, ma è più facile capire come funziona. L'intera macchina a stati guidata da tabelle può essere spiegata, ma di solito finisce per essere scatole nere.
Eclipse

La risposta di Cody Brocious che menziona "la compilazione di alberi di analisi" indicava anche quest'area: analisi / interpretazione / compilazione del linguaggio.
imz - Ivan Zakharyaschev

9

Disabilitazione / impostazione di sola lettura per tutti i controlli figlio in un controllo contenitore. Avevo bisogno di farlo perché alcuni dei controlli figli erano essi stessi contenitori.

public static void SetReadOnly(Control ctrl, bool readOnly)
{
    //set the control read only
    SetControlReadOnly(ctrl, readOnly);

    if (ctrl.Controls != null && ctrl.Controls.Count > 0)
    {
        //recursively loop through all child controls
        foreach (Control c in ctrl.Controls)
            SetReadOnly(c, readOnly);
    }
}

8

Famoso ciclo Eval / Apply di SICP

testo alternativo
(fonte: mit.edu )

Ecco la definizione di eval:

(define (eval exp env)
  (cond ((self-evaluating? exp) exp)
        ((variable? exp) (lookup-variable-value exp env))
        ((quoted? exp) (text-of-quotation exp))
        ((assignment? exp) (eval-assignment exp env))
        ((definition? exp) (eval-definition exp env))
        ((if? exp) (eval-if exp env))
        ((lambda? exp)
         (make-procedure (lambda-parameters exp)
                         (lambda-body exp)
                         env))
        ((begin? exp) 
         (eval-sequence (begin-actions exp) env))
        ((cond? exp) (eval (cond->if exp) env))
        ((application? exp)
         (apply (eval (operator exp) env)
                (list-of-values (operands exp) env)))
        (else
         (error "Unknown expression type - EVAL" exp))))

Ecco la definizione di applicare:

(define (apply procedure arguments)
  (cond ((primitive-procedure? procedure)
         (apply-primitive-procedure procedure arguments))
        ((compound-procedure? procedure)
         (eval-sequence
           (procedure-body procedure)
           (extend-environment
             (procedure-parameters procedure)
             arguments
             (procedure-environment procedure))))
        (else
         (error
          "Unknown procedure type - APPLY" procedure))))

Ecco la definizione di eval-sequenza:

(define (eval-sequence exps env)
  (cond ((last-exp? exps) (eval (first-exp exps) env))
        (else (eval (first-exp exps) env)
              (eval-sequence (rest-exps exps) env))))

eval-> apply->eval-sequence ->eval


7

La ricorsione viene utilizzata in cose come gli alberi BSP per il rilevamento delle collisioni nello sviluppo di giochi (e altre aree simili).


7

Le persone spesso ordinano pile di documenti utilizzando un metodo ricorsivo. Ad esempio, immagina di ordinare 100 documenti con nomi sopra. Per prima cosa, metti i documenti in pile in base alla prima lettera, quindi ordina ogni pila.

La ricerca delle parole nel dizionario viene spesso eseguita con una tecnica simile alla ricerca binaria, che è ricorsiva.

Nelle organizzazioni, i capi spesso danno comandi ai capi dipartimento, che a loro volta danno comandi ai manager e così via.


5

Requisiti del mondo reale che ho ricevuto di recente:

Requisito A: implementare questa funzionalità dopo aver compreso a fondo il Requisito A.


4

Parser e compilatori possono essere scritti in un metodo di discesa ricorsiva. Non è il modo migliore per farlo, poiché strumenti come lex / yacc generano parser più veloci ed efficienti, ma concettualmente semplici e facili da implementare, quindi rimangono comuni.


4

La ricorsione viene applicata a problemi (situazioni) in cui è possibile suddividerla (ridurla) in parti più piccole e ogni parte (s) sembra simile al problema originale.

Buoni esempi di dove sono le cose che contengono parti più piccole simili a se stessa:

  • struttura ad albero (un ramo è come un albero)
  • liste (parte di una lista è ancora una lista)
  • contenitori (bambole russe)
  • sequenze (parte di una sequenza assomiglia alla successiva)
  • gruppi di oggetti (un sottogruppo è ancora un gruppo di oggetti)

La ricorsione è una tecnica per continuare a scomporre il problema in pezzi sempre più piccoli, fino a quando uno di quei pezzi diventa abbastanza piccolo da essere un pezzo di torta. Naturalmente, dopo averli scomposti, devi "ricucire" i risultati nell'ordine giusto per formare una soluzione totale del tuo problema originale.

Alcuni algoritmi di ordinamento ricorsivo, algoritmi di tree-walking, algoritmi di mappatura / riduzione, divide et impera sono tutti esempi di questa tecnica.

Nella programmazione di computer, la maggior parte dei linguaggi di tipo call-return basati su stack hanno già le capacità integrate per la ricorsione: ie

  • scomporre il problema in parti più piccole ==> chiama se stesso su un sottoinsieme più piccolo dei dati originali),
  • tenere traccia di come sono divisi i pezzi ==> stack di chiamate,
  • ricucire i risultati ==> ritorno basato sullo stack


4

Alcuni ottimi esempi di ricorsione si trovano nei linguaggi di programmazione funzionale . Nei linguaggi di programmazione funzionale ( Erlang , Haskell , ML / OCaml / F # , ecc.), È molto comune che qualsiasi elaborazione di elenchi utilizzi la ricorsione.

Quando si ha a che fare con elenchi in tipici linguaggi in stile OOP imperativo, è molto comune vedere elenchi implementati come elenchi concatenati ([item1 -> item2 -> item3 -> item4]). Tuttavia, in alcuni linguaggi di programmazione funzionale, si scopre che gli elenchi stessi sono implementati in modo ricorsivo, dove la "testa" dell'elenco punta al primo elemento dell'elenco e la "coda" punta a un elenco contenente il resto degli elementi ( [elemento1 -> [elemento2 -> [elemento3 -> [elemento4 -> []]]]]). È piuttosto creativo secondo me.

Questa gestione delle liste, se combinata con la corrispondenza dei modelli, è MOLTO potente. Diciamo che voglio sommare un elenco di numeri:

let rec Sum numbers =
    match numbers with
    | [] -> 0
    | head::tail -> head + Sum tail

Questo essenzialmente dice "se siamo stati chiamati con una lista vuota, restituisci 0" (permettendoci di interrompere la ricorsione), altrimenti restituisce il valore di head + il valore di Sum chiamato con gli elementi rimanenti (quindi, la nostra ricorsione).

Ad esempio, potrei avere un elenco di URL , penso che suddivida tutti gli URL a cui si collega ogni URL, quindi riduco il numero totale di link a / da tutti gli URL per generare "valori" per una pagina (un approccio che Google prende con PageRank e che puoi trovare definito nel documento originale MapReduce ). Puoi farlo anche per generare il conteggio delle parole in un documento. E molte, molte, molte altre cose.

Puoi estendere questo modello funzionale a qualsiasi tipo di codice MapReduce in cui puoi prendere un elenco di qualcosa, trasformarlo e restituire qualcos'altro (che sia un altro elenco o qualche comando zip nell'elenco).


3

XML, o attraversando qualsiasi cosa che sia un albero. Anche se, ad essere onesti, non uso quasi mai la ricorsione nel mio lavoro.


Nemmeno la ricorsione della coda?
Apocalisp

Ho usato la ricorsione una volta nella mia carriera e quando il framework è cambiato, me ne sono sbarazzato. L'80% di ciò che facciamo è CRUD.
Charles Graham,

1
Menzionare "XML" in primo luogo è piuttosto strano. Non è una cosa naturale, non qualcosa con cui una persona normale a cui insegnerai ha a che fare nella vita di tutti i giorni. Ma l'idea è ovviamente abbastanza sensata.
imz - Ivan Zakharyaschev

3

Circuiti di feedback in un'organizzazione gerarchica.

Il capo superiore dice ai dirigenti di alto livello di raccogliere feedback da tutti i membri dell'azienda.

Ogni dirigente raccoglie i suoi rapporti diretti e dice loro di raccogliere feedback dai loro rapporti diretti.

E su tutta la linea.

Le persone senza rapporti diretti, i nodi foglia dell'albero, danno il loro feedback.

Il feedback torna indietro nell'albero con ogni manager che aggiunge il proprio feedback.

Alla fine tutto il feedback lo fa tornare al capo superiore.

Questa è la soluzione naturale perché il metodo ricorsivo consente il filtraggio a ogni livello: la raccolta dei duplicati e la rimozione del feedback offensivo. Il capo superiore potrebbe inviare un'e-mail globale e chiedere a ciascun dipendente di riportare il feedback direttamente a lui / lei, ma ci sono i problemi "non puoi gestire la verità" e "sei licenziato", quindi la ricorsione funziona meglio qui.


2

Supponiamo che tu stia creando un CMS per un sito Web, in cui le tue pagine sono in una struttura ad albero, con la radice, ad esempio, la home page.

Supponiamo che anche il tuo {utente | cliente | cliente | capo} richieda di inserire una traccia di breadcrumb in ogni pagina per mostrare dove ti trovi nell'albero.

Per ogni data pagina n, potresti voler raggiungere il genitore di n, e il suo genitore, e così via, ricorsivamente per costruire un elenco di nodi fino alla radice dell'albero della pagina.

Ovviamente, stai premendo il db più volte per pagina in questo esempio, quindi potresti voler usare un alias SQL in cui cerchi la tabella delle pagine come a e la tabella delle pagine di nuovo come b, e unirti a a.id con b.parent in modo da fare in modo che il database esegua i join ricorsivi. È passato un po 'di tempo, quindi la mia sintassi probabilmente non è utile.

Poi di nuovo, potresti volerlo solo calcolare una volta e memorizzarlo con il record della pagina, aggiornandolo solo se sposti la pagina. Probabilmente sarebbe più efficiente.

Comunque, questo è il mio $ 0,02


2

Hai un albero dell'organizzazione profondo N livelli. Molti dei nodi vengono selezionati e si desidera espandere solo i nodi che sono stati selezionati.

Questo è qualcosa che ho effettivamente codificato. È bello e facile con la ricorsione.


2

Nel mio lavoro abbiamo un sistema con una struttura dati generica che può essere descritta come un albero. Ciò significa che la ricorsione è una tecnica molto efficace per lavorare con i dati.

Risolverlo senza ricorsione richiederebbe molto codice non necessario. Il problema con la ricorsione è che non è facile seguire ciò che accade. Devi davvero concentrarti quando segui il flusso dell'esecuzione. Ma quando funziona il codice è elegante ed efficace.



2
  • Analisi di un file XML .
  • Ricerca efficiente in spazi multidimensionali. Per esempio. quad-tree in 2D, oct-tree in 3D, kd-trees, ecc.
  • Clustering gerarchico.
  • A pensarci bene, attraversare qualsiasi struttura gerarchica si presta naturalmente alla ricorsione.
  • La metaprogrammazione dei modelli in C ++, dove non ci sono cicli e la ricorsione è l'unico modo.

"XML" non è essenziale per l'idea di questa risposta (e menzionare in modo specifico XML potrebbe disgustare / annoiare le persone a cui insegni). Qualsiasi linguaggio tipico (un linguaggio per computer o uno naturale) sarebbe un esempio per un problema di analisi ricorsiva.
imz - Ivan Zakharyaschev

Il poster chiedeva "problemi del mondo reale in cui un approccio ricorsivo è la soluzione naturale". L'analisi di un file xml è certamente un problema del mondo reale e si presta naturalmente alla ricorsione. Il fatto che sembri avere una strana avversione per XML non cambia il fatto che sia ampiamente utilizzato.
Dima


2

Il miglior esempio che conosco è il quicksort , è molto più semplice con la ricorsione. Date un'occhiata al:

shop.oreilly.com/product/9780596510046.do

www.amazon.com/Beautiful-Code-Leading-Programmers-Practice/dp/0596510047

(Clicca sul primo sottotitolo sotto il capitolo 3: "Il codice più bello che abbia mai scritto").


1
E anche MergeSort è più semplice con la ricorsione.
Matthew Schinckel

1
Il collegamento è interrotto. Puoi aggiungere il titolo del libro?
Peter Mortensen

@PeterMortensen, il libro è Beautiful Code di Greg Wilson e Andy Oram. Ho aggiornato il collegamento, anche se sembra che O'Reilly non permetta più di sbirciare dentro. Ma puoi dare un'occhiata ad Amazon.
Fabio Ceconello

1

Le società telefoniche e via cavo mantengono un modello della loro topologia di cablaggio, che in effetti è una rete o un grafico di grandi dimensioni. La ricorsione è un modo per attraversare questo modello quando si desidera trovare tutti gli elementi padre o tutti i figli.

Poiché la ricorsione è costosa dal punto di vista dell'elaborazione e della memoria, questo passaggio viene comunemente eseguito solo quando la topologia viene modificata e il risultato viene memorizzato in un formato di elenco preordinato modificato.


1

Il ragionamento induttivo, il processo di formazione del concetto, è di natura ricorsiva. Il tuo cervello lo fa sempre, nel mondo reale.


1

Idem il commento sui compilatori. I nodi dell'albero della sintassi astratta si prestano naturalmente alla ricorsione. Anche tutte le strutture dati ricorsive (liste collegate, alberi, grafici, ecc.) Sono gestite più facilmente con la ricorsione. Penso che la maggior parte di noi non riesca a usare molto la ricorsione una volta terminati gli studi a causa dei tipi di problemi del mondo reale, ma è bene esserne consapevoli come opzione.


1

La moltiplicazione dei numeri naturali è un esempio reale di ricorsione:

To multiply x by y
  if x is 0
    the answer is 0
  if x is 1
    the answer is y
  otherwise
    multiply x - 1 by y, and add x
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.