Un ciclo while è intrinsecamente una ricorsione?


37

Mi chiedevo se un ciclo while fosse intrinsecamente una ricorsione?

Penso che sia perché un ciclo while può essere visto come una funzione che si chiama alla fine. Se non è ricorsione, allora qual è la differenza?



13
È possibile convertire la ricorsione in iterazione e viceversa, sì. Ciò non significa che siano uguali, hanno solo le stesse capacità. Ci sono momenti in cui la ricorsione è più naturale e ci sono momenti in cui l'iterazione è più naturale.
Polygnome,

18
@MooingDuck Puoi indurre per induzione che qualsiasi ricorsione può essere scritta come iterazione e viceversa. Sì, sembrerà molto diverso, ma puoi farlo comunque.
Polygnome,

6
Cosa significa intrinsecamente lo stesso qui? In programmazione, usare la ricorsione significa una cosa specifica, che è diversa dall'iterazione (loop). In CS, quando ti avvicini al lato teorico della matematica delle cose, queste cose iniziano a significare cose un po 'diverse.
hyde,

3
@MooingDuck La conversione da ricorsiva a iterativa è in realtà piuttosto banale. Basta tenere una pila di parametri di chiamata di funzione e una pila di risultati per le chiamate di funzione. Sostituisci le chiamate ricorsive aggiungendo i parametri allo stack di chiamate. certo c'è tutta la gestione dello stack che spezza un po 'la struttura dell'algoritmo, ma una volta capito questo è abbastanza facile vedere che il codice fa la stessa cosa. Fondamentalmente stai scrivendo esplicitamente lo stack di chiamate implicito nelle definizioni ricorsive.
Bakuriu,

Risposte:


116

I loop sono molto non ricorsione. In realtà, sono il primo esempio del meccanismo opposto : iterazione .

Il punto di ricorsione è che un elemento dell'elaborazione chiama un'altra istanza di se stesso. La macchina di controllo ad anello semplicemente salta di nuovo al punto in cui è iniziato.

Saltare nel codice e chiamare un altro blocco di codice sono operazioni diverse. Ad esempio, quando si salta all'inizio del loop, la variabile di controllo del loop ha ancora lo stesso valore che aveva prima del salto. Ma se chiami un'altra istanza della routine in cui ti trovi, allora la nuova istanza ha copie nuove e non correlate di tutte le sue variabili. In effetti, una variabile può avere un valore al primo livello di elaborazione e un altro valore a un livello inferiore.

Questa funzionalità è cruciale per il funzionamento di molti algoritmi ricorsivi ed è per questo che non è possibile emulare la ricorsione tramite iterazione senza gestire anche una pila di frame chiamati che tiene traccia di tutti quei valori.


10
@Giorgio Potrebbe essere vero, ma è un commento su un reclamo che la risposta non ha fatto. "Arbitrariamente" non è presente in questa risposta e cambierebbe significativamente il significato.
hvd,

12
@hvd In linea di principio, la ricorsione della coda è una ricorsione completa come qualsiasi altra. I compilatori intelligenti possono ottimizzare l'effettiva parte "creazione di un nuovo stack frame" in modo che il codice generato sia molto simile a un ciclo, ma i concetti di cui stiamo parlando si applicano al livello del codice sorgente. Considero la forma che un algoritmo ha come codice sorgente la cosa importante, quindi la chiamerei comunque ricorsione
Kilian Foth

15
@Giorgio "questo è esattamente ciò che fa la ricorsione: chiamarsi con nuovi argomenti" - ad eccezione della chiamata. E gli argomenti.
Hobbs

12
@Giorgio Stai usando definizioni di parole diverse rispetto alla maggior parte qui. Le parole, sai, sono la base della comunicazione. Sono programmatori, non CS Stack Exchange. Se usassimo parole come "argomento", "chiamata", "funzione" ecc. Come suggerisci, sarebbe impossibile discutere del codice reale.
hyde,

6
@Giorgio Sto guardando il concetto astratto. C'è il concetto di ricorrenza e il concetto di loop. Sono concetti diversi . Hobbs ha ragione al 100% che non ci sono argomenti e non c'è chiamata. Sono fondamentalmente e astrattamente differenti. E questo va bene perché risolvono diversi problemi. D'altra parte, stai esaminando come implementare i loop quando il tuo unico strumento è la ricorsione. Ironia della sorte, stai dicendo a Hobbs di smettere di pensare all'implementazione e di iniziare a guardare i concetti quando la tua metodologia è quella che ha davvero bisogno di una nuova valutazione.
corsiKa

37

Dire che X è intrinsecamente Y ha senso solo se hai in mente un sistema (formale) in cui stai esprimendo X. Se definisci la semantica whilein termini di calcolo lambda, potresti menzionare la ricorsione *; se lo definisci in termini di macchina del registro, probabilmente non lo farai.

In entrambi i casi, probabilmente le persone non ti capiranno se chiami una funzione ricorsiva solo perché contiene un ciclo while.

* Anche se forse solo indirettamente, ad esempio se lo definisci in termini di fold.


4
Ad essere onesti, la funzione non è ricorsiva in nessuna definizione. Contiene solo un elemento ricorsivo, il loop.
Luaan,

@Luaan: Sicuramente, ma dato che nelle lingue con un whilecostrutto la ricorsività è generalmente una proprietà delle funzioni, non riesco proprio a pensare a nient'altro da descrivere come "ricorsivo" in questo contesto.
Anton Golov,

36

Questo dipende dal tuo punto di vista.

Se osservi la teoria della calcolabilità , l'iterazione e la ricorsione sono ugualmente espressive . Ciò significa che puoi scrivere una funzione che calcola qualcosa e non importa se lo fai in modo ricorsivo o iterativo, sarai in grado di scegliere entrambi gli approcci. Non c'è nulla che tu possa calcolare ricorsivamente che non puoi calcolare iterativamente e viceversa (sebbene il funzionamento interno del programma potrebbe essere diverso).

Molti linguaggi di programmazione non trattano la ricorsione e l'iterazione allo stesso modo, e per buoni motivi. Di solito , la ricorsione significa che il linguaggio / compilatore gestisce lo stack di chiamate e l'iterazione indica che potrebbe essere necessario eseguire autonomamente la gestione dello stack.

Tuttavia, ci sono lingue - in particolare linguaggi funzionali - in cui cose come i loop (per, mentre) sono davvero solo zucchero sintattico per la ricorsione e implementate dietro le quinte in quel modo. Questo è spesso desiderabile nei linguaggi funzionali, perché di solito non hanno il concetto di looping altrimenti, e aggiungerlo renderebbe il loro calcolo più complesso, per ragioni pratiche.

Quindi no, non sono intrinsecamente uguali . Sono ugualmente espressivi , il che significa che non puoi calcolare qualcosa in modo iterativo, non puoi calcolare ricorsivamente e viceversa, ma questo è tutto, nel caso generale (secondo la tesi di Church-Turing).

Si noti che stiamo parlando di programmi ricorsivi qui. Esistono altre forme di ricorsione, ad esempio nelle strutture di dati (ad esempio alberi).


Se lo guardi da un punto di vista dell'implementazione , la ricorsione e l'iterazione non sono praticamente la stessa cosa. La ricorsione crea un nuovo frame di stack per ogni chiamata. Ogni fase della ricorsione è autonoma, ottenendo gli argomenti per il calcolo dalla chiamata (stessa).

I loop invece non creano cornici di chiamata. Per loro, il contesto non viene conservato su ogni passaggio. Per il loop, il programma torna semplicemente all'inizio del loop fino a quando la condizione del loop fallisce.

Questo è abbastanza importante da sapere, dal momento che può fare differenze abbastanza radicali nel mondo reale. Per la ricorsione, l'intero contesto deve essere salvato su ogni chiamata. Per l'iterazione, hai un controllo preciso su quali variabili sono in memoria e su cosa viene salvato dove.

Se lo guardi in questo modo, vedi rapidamente che per la maggior parte delle lingue, iterazione e ricorsione sono fondamentalmente diverse e hanno proprietà diverse. A seconda della situazione, alcune delle proprietà sono più desiderabili di altre.

Ricorsione può rendere i programmi più semplice e più facile da testare e la prova . La conversione di una ricorsione in iterazione solitamente rende il codice più complesso, aumentando la probabilità di errore. D'altro canto, la conversione in iterazione e la riduzione della quantità di frame dello stack di chiamate possono far risparmiare molta memoria necessaria.


Un linguaggio con variabili locali e ricorsione ma senza array potrebbe eseguire attività che non potrebbero essere eseguite da un linguaggio iterativo con variabili locali e senza array. Ad esempio, segnalare se un input contiene una stringa alfanumerica di lunghezza sconosciuta seguita da uno spazio vuoto e quindi i caratteri della stringa originale in ordine inverso.
supercat

3
Finché la lingua è completa, può farlo. Un array può essere facilmente sostituito da un elenco (doppiamente) collegato, ad esempio. Parlare di iterazione o ricorsione e se sono uguali ha senso solo se si confrontano due lingue complete di turing.
Polygnome,

Intendevo non avere nient'altro che semplici variabili statiche o automatiche, cioè non essere Turing-complete. Un linguaggio puramente iterativo sarebbe limitato a quei compiti che possono essere svolti tramite un semplice automa finito deterministico, mentre un linguaggio ricorsivo aggiungerebbe la capacità di eseguire compiti che richiederebbero almeno un automa finito deterministico pushdown.
supercat

1
Se la lingua non è completa, è inutile cominciare. I DFA non possono eseguire iterazioni arbitrarie né ricorsioni tra l'altro.
Polygnome,

2
Nessuna implementazione è in realtà completa di Turing e le lingue che non sono complete di Turing possono essere utili per molti scopi. Qualsiasi programma che può essere eseguito con un numero finito di variabili con un intervallo finito può essere ospitato da un DFA in cui ogni possibile combinazione di valori è uno stato discreto.
supercat

12

La differenza è lo stack implicito e semantico.

Un ciclo while che "si chiama alla fine" non ha stack di cui eseguire il backup. L'ultima iterazione imposta quale sarà lo stato al termine.

Tuttavia, la ricorsione non può essere eseguita senza questo stack implicito che ricorda lo stato del lavoro svolto in precedenza.

È vero che è possibile risolvere qualsiasi problema di ricorsione con iterazione se si dà accesso a uno stack in modo esplicito. Ma farlo in questo modo non è lo stesso.

La differenza semantica ha a che fare con il fatto che guardare il codice ricorsivo trasmette un'idea in un modo completamente diverso dal codice iterativo. Il codice iterativo fa le cose un passo alla volta. Accetta qualsiasi stato proveniente da prima e funziona solo per creare lo stato successivo.

Il codice ricorsivo suddivide un problema in frattali. Questa piccola parte assomiglia a quella grande parte in modo che possiamo fare solo un po 'di esso e quel po' allo stesso modo. È un modo diverso di pensare ai problemi. È molto potente e ci vuole l'abitudine. Si può dire molto in poche righe. Non puoi tirarlo fuori da un ciclo while anche se ha accesso a uno stack.


5
Penso che lo "stack implicito" sia fuorviante. La ricorsione fa parte della semantica di una lingua, non un dettaglio di implementazione. (Concesso, la maggior parte delle lingue che supportano la ricorsione usano uno stack di chiamate; ma in primo luogo, alcune di queste lingue non lo fanno e, in secondo luogo, anche nelle lingue che lo fanno, non tutte le chiamate ricorsive si aggiungono necessariamente allo stack di chiamate, poiché molte lingue supportano ottimizzazioni come come eliminazione delle chiamate di coda .) Comprendere l'implementazione normale / semplice può essere utile per capire l'astrazione, ma non dovresti indurti a pensare che sia l'intera storia.
Ruakh,

2
@ruakh Direi che una funzione che viene eseguita in uno spazio costante usando l'eliminazione della coda è davvero un ciclo. Qui lo stack non è il dettaglio dell'implementazione, è l'astrazione che consente di accumulare stati diversi per diversi livelli di ricorsione.
Cimbali,

@ruakh: qualsiasi stato all'interno di una singola chiamata ricorsiva verrà memorizzato su uno stack implicito, a meno che la ricorsione non possa essere convertita dal compilatore in un ciclo iterativo. L'eliminazione della chiamata di coda è un dettaglio di implementazione, quello di cui devi essere consapevole se vuoi riorganizzare la tua funzione in modo ricorsivo della coda. Inoltre, "poche di queste lingue non lo fanno" - puoi fornire un esempio di lingue che non necessitano di uno stack per le chiamate ricorsive?
Groo,


@ruakh: CPS da solo crea lo stesso stack implicito, quindi deve fare affidamento sull'eliminazione delle chiamate di coda per avere un senso (cosa che diventa banale a causa del modo in cui è costruita). Anche l'articolo di Wikipedia a cui ti sei collegato dice lo stesso: l' uso di CPS senza ottimizzazione delle chiamate di coda (TCO) farà sì che non solo la continuazione costruita cresca potenzialmente durante la ricorsione, ma anche lo stack di chiamate .
Groo,

7

Tutto dipende dal tuo uso intrinseco del termine . A livello di linguaggio di programmazione, sono sintatticamente e semanticamente diversi e hanno prestazioni e uso della memoria piuttosto diversi. Ma se si scava abbastanza in profondità nella teoria, possono essere definiti l'uno nell'altro, ed è quindi "lo stesso" in un certo senso teorico.

La vera domanda è: quando ha senso distinguere tra iterazione (loop) e ricorsione, e quando è utile pensarla come le stesse cose? La risposta è che quando si esegue effettivamente la programmazione (anziché scrivere prove matematiche) è importante distinguere tra iterazione e ricorsione.

La ricorsione crea un nuovo frame di stack, ovvero un nuovo set di variabili locali per ogni chiamata. Questo ha un sovraccarico e occupa spazio nello stack, il che significa che una ricorsione abbastanza profonda può traboccare lo stack causando l'arresto anomalo del programma. L'iterazione, d'altra parte, modifica solo le variabili esistenti, quindi è generalmente più veloce e occupa solo una quantità costante di memoria. Quindi questa è una distinzione molto importante per uno sviluppatore!

Nelle lingue con ricorsione di coda (in genere linguaggi funzionali), il compilatore potrebbe essere in grado di ottimizzare le chiamate ricorsive in modo tale da occupare solo una quantità costante di memoria. In queste lingue l'importante distinzione non è iterazione vs ricorsione, ma versione non-coda-ricorsione coda-chiamata-ricorsione e iterazione.

In conclusione: devi essere in grado di dire la differenza, altrimenti il ​​tuo programma si arresterà in modo anomalo.


3

whilei loop sono una forma di ricorsione, vedi ad esempio la risposta accettata a questa domanda . Corrispondono all'operatore μ nella teoria della calcolabilità (vedere ad esempio qui ).

Tutte le varianti di forloop che ripetono su un intervallo di numeri, una raccolta finita, una matrice e così via, corrispondono alla ricorsione primitiva, vedi ad esempio qui e qui . Si noti che i forloop di C, C ++, Java e così via sono in realtà zucchero sintattico per un whileloop, e quindi non corrispondono alla ricorsione primitiva. Il forciclo di Pascal è un esempio di ricorsione primitiva.

Una differenza importante è che la ricorsione primitiva termina sempre, mentre la ricorsione generalizzata ( whileloop) potrebbe non terminare.

MODIFICARE

Alcuni chiarimenti riguardanti commenti e altre risposte. "La ricorsione si verifica quando una cosa è definita in termini di se stessa o del suo tipo." (vedi Wikipedia ). Così,

Un ciclo while è intrinsecamente una ricorsione?

Dal momento che è possibile definire un whileciclo in termini di se stesso

while p do c := if p then (c; while p do c))

quindi, , un whileciclo è una forma di ricorsione. Le funzioni ricorsive sono un'altra forma di ricorsione (un altro esempio di definizione ricorsiva). Elenchi e alberi sono altre forme di ricorsione.

Un'altra domanda implicitamente assunta da molte risposte e commenti è

I loop while e le funzioni ricorsive sono equivalenti?

La risposta a questa domanda è no : un whileloop corrisponde a una funzione ricorsiva della coda, dove le variabili a cui accede il loop corrispondono agli argomenti della funzione ricorsiva implicita, ma, come altri hanno sottolineato, funzioni non ricorsive della coda non può essere modellato da un whileloop senza usare uno stack aggiuntivo.

Quindi, il fatto che "un whileciclo sia una forma di ricorsione" non contraddice il fatto che "alcune funzioni ricorsive non possono essere espresse da un whileciclo".


2
@morbidCode: la ricorsione primitiva e la ricorsione μ sono forme di ricorsione con restrizioni specifiche (o mancanza di esse), studiate ad esempio nella teoria della calcolabilità. A quanto pare, una lingua con solo un FORciclo può calcolare esattamente tutte le funzioni ricorsive primitive e una lingua con solo un WHILEciclo può calcolare esattamente tutte le funzioni ricorsive µ (e si scopre che le funzioni ricorsive µ sono esattamente quelle funzioni che una macchina di Turing può calcolare). Oppure, per farla breve: ricorsione primitiva e ricorsione µ sono termini tecnici della teoria matematica / calcolabilità.
Jörg W Mittag,

2
Pensavo che la "ricorsione" implicava una funzione che si chiamava, determinando lo stato di esecuzione corrente nello stack e così via; pertanto la maggior parte delle macchine ha un limite pratico su quanti livelli puoi contare. Mentre i loop non hanno tali limiti perché internamente userebbero qualcosa come un "JMP" e non userebbero lo stack. Solo la mia comprensione, potrebbe essere sbagliata.
Jay,

13
Questa risposta sta usando una definizione completamente diversa per la parola "ricorsiva" rispetto a quella dell'OP, ed è quindi altamente fuorviante.
Mooing Duck,

2
@DavidGrinberg: Citando: "C, C ++, Java per i loop non sono un esempio di ricorsione primitiva. La ricorsione primitiva significa che il numero massimo di iterazioni / profondità di ricorsione viene fissato prima di iniziare il ciclo." Giorgio sta parlando dei primitivi della teoria della computabilità . Non correlato ai linguaggi di programmazione.
Mooing Duck,

3
Sono d'accordo con Mooing Duck. Mentre la teoria della computabilità potrebbe essere interessante nel CS teorico, penso che tutti concordino sul fatto che l'OP stava parlando del concetto di linguaggi di programmazione.
Voo,

2

Una chiamata di coda (o chiamata ricorsiva di coda) è esattamente implementata come un "goto con argomenti" (senza spingere alcun frame di chiamata aggiuntivo nello stack di chiamate ) e in alcuni linguaggi funzionali (in particolare Ocaml) è il solito modo di eseguire il loop.

Quindi un ciclo while (nelle lingue che li hanno) può essere visto come terminando con una chiamata di coda al suo corpo (o al suo test di testa).

Allo stesso modo, le chiamate ricorsive ordinarie (non di coda) possono essere simulate mediante loop (usando un po 'di stack).

Leggi anche le continuazioni e lo stile del passaggio di continuazione .

Quindi "ricorsione" e "iterazione" sono profondamente equivalenti.


1

È vero che sia la ricorsione che i loop continui illimitati sono equivalenti in termini di espressività computazionale. Cioè, qualsiasi programma scritto in modo ricorsivo può essere riscritto in un programma equivalente usando invece i loop e viceversa. Entrambi gli approcci sono completi di turing , che possono essere utilizzati per calcolare qualsiasi funzione calcolabile.

La differenza fondamentale in termini di programmazione è che la ricorsione consente di utilizzare i dati che vengono memorizzati nello stack di chiamate. Per illustrare ciò, si supponga di voler stampare un elemento di un elenco collegato singolarmente utilizzando un ciclo o una ricorsione. Userò C per il codice di esempio:

 typedef struct List List;
 struct List
 {
     List* next;
     int element;
 };

 void print_list_loop(List* l)
 {
     List* it = l;
     while(it != NULL)
     {
          printf("Element: %d\n", it->element);
          it = it->next;
     }
 }

 void print_list_rec(List* l)
 {
      if(l == NULL) return;
      printf("Element: %d\n", l->element);
      print_list_rec(l->next);
 }

Semplice vero? Ora facciamo una leggera modifica: stampa l'elenco in ordine inverso.

Per la variante ricorsiva, questa è una modifica quasi banale alla funzione originale:

void print_list_reverse_rec(List* l)
{
    if (l == NULL) return;
    print_list_reverse_rec(l->next);
    printf("Element: %d\n", l->element);
}

Per la funzione loop, tuttavia, abbiamo un problema. La nostra lista è collegata singolarmente e quindi può essere percorsa solo in avanti. Ma poiché stiamo stampando al contrario, dobbiamo iniziare a stampare l'ultimo elemento. Una volta raggiunto l'ultimo elemento, non possiamo più tornare al penultimo elemento.

Quindi, o dobbiamo effettuare molti re-traversing, oppure dobbiamo costruire una struttura di dati ausiliari che tenga traccia degli elementi visitati e dai quali possiamo quindi stampare in modo efficiente.

Perché non abbiamo questo problema con la ricorsione? Perché in ricorsione abbiamo già in atto una struttura di dati ausiliari: lo stack di chiamate di funzione.

Poiché la ricorsione ci consente di tornare alla precedente invocazione della chiamata ricorsiva, con tutte le variabili locali e lo stato per quella chiamata ancora intatti, otteniamo una certa flessibilità che sarebbe noioso da modellare nel caso iterativo.


1
Naturalmente, la seconda funzione ricorsiva non è ricorsiva alla coda: è molto più difficile da ottimizzare per lo spazio in quanto non è possibile utilizzare il TCO per riutilizzare lo stack. L'implementazione di un elenco doppiamente collegato renderebbe entrambi gli algoritmi banali in entrambi i modi, a costo dello spazio di un puntatore / riferimento per elemento.
Baldrickk,

@Baldrickk La cosa divertente della ricorsione della coda è che si finisce con una versione che è molto più vicina a come sarebbe stata la versione del ciclo, poiché rimuove nuovamente la capacità di memorizzare lo stato nello stack di chiamate. Un elenco doppiamente collegato lo risolverebbe, ma la riprogettazione della struttura dei dati spesso non è un'opzione quando si verifica questo problema. Mentre l'esempio qui è in qualche modo vincolato artificialmente, illustra uno schema che compare frequentemente in linguaggi funzionali nel contesto di tipi algebrici ricorsivi.
ComicSansMS,

Il mio punto era che se ti imbatti in questo problema, dipende più da una mancanza di design funzionale, che da quale linguaggio costruisci per implementarlo, e ogni scelta ha i suoi aspetti positivi e negativi :)
Baldrickk,

0

I loop sono una forma speciale di ricorsione per raggiungere un compito specifico (principalmente iterazione). È possibile implementare un loop in uno stile ricorsivo con le stesse prestazioni [1] in diverse lingue. e nel SICP [2], puoi vedere che i loop sono descritti come "zucchero sintetico". Nella maggior parte dei linguaggi di programmazione imperativi, per e mentre i blocchi utilizzano lo stesso ambito della loro funzione padre. Nondimeno, nella maggior parte dei linguaggi di programmazione funzionale non esiste né per né mentre esistono loop perché non ce n'è bisogno.

Il motivo per cui le lingue imperative hanno per / mentre i cicli è che stanno gestendo gli stati mutandoli. Ma in realtà, se guardi da una prospettiva diversa, se pensi a un blocco while come una funzione stessa, prendendo il parametro, elaborandolo e restituendo un nuovo stato - che potrebbe anche essere il richiamo della stessa funzione con parametri diversi - tu può pensare al loop come a una ricorsione.

Il mondo potrebbe anche essere definito mutabile o immutabile. se definiamo il mondo come un insieme di regole e chiamiamo una funzione ultima che prende tutte le regole e lo stato corrente come parametri e restituiamo il nuovo stato in base a questi parametri che ha la stessa funzionalità (genera lo stato successivo nello stesso modo), potremmo anche dire che è una ricorsione e un ciclo.

nel seguente esempio, la vita è la funzione prende due parametri "regole" e "stato", e il nuovo stato verrà costruito nel prossimo tick.

life rules state = life rules new_state
    where new_state = construct_state_in_time rules state

[1]: l'ottimizzazione delle chiamate di coda è un'ottimizzazione comune nei linguaggi di programmazione funzionale per utilizzare lo stack di funzioni esistente nelle chiamate ricorsive anziché crearne uno nuovo.

[2]: Struttura e interpretazione dei programmi per computer, MIT. https://mitpress.mit.edu/books/structure-and-interpretation-computer-programs


4
@Giorgio Non è il mio downvote, ma solo un'ipotesi: penso che la maggior parte dei programmatori ritenga che la ricorsione implichi una chiamata di funzione ricorsiva, perché, beh, questo è ciò che è la ricorsione, una funzione che si chiama da sola. In un ciclo, non vi è alcuna chiamata di funzione ricorsiva. Quindi dire che un ciclo senza chiamata di funzione ricorsiva è una forma speciale di ricorsione sarebbe palesemente sbagliato, se si va da questa definizione.
hyde,

1
Beh, forse guardandolo ad un punto di vista più astratto, quelle che sembrano cose diverse, sono concettualmente le stesse. Trovo abbastanza scoraggiante e triste pensare che le persone sottovalutino le risposte solo perché non corrispondono alle loro aspettative invece di prenderle come un'opportunità per imparare qualcosa. Tutte le risposte che provano a dire: "Ehi, guarda, queste cose sembrano diverse in superficie, ma in realtà sono le stesse a un livello più astratto" sono state sottovalutate.
Giorgio,

3
@Georgio: lo scopo di questo sito è di ottenere risposte alle domande. Le risposte utili e corrette meritano voti positivi, le risposte confuse e inutili meritano voti negativi. Una risposta che usa sottilmente una diversa definizione di un termine comune senza chiarire quale diversa definizione viene utilizzata è confusa e inutile. Risposte che hanno senso solo se conosci già la risposta, per così dire, non sono utili e servono solo a mostrare agli scrittori una comprensione superiore della terminologia.
Jacques B

2
@JacquesB: "Risposte che hanno senso solo se conosci già la risposta, per così dire, non sono utili ...": Questo si può dire anche di risposte che confermano solo ciò che il lettore già sa o pensa di sapere. Se una risposta introduce una terminologia non chiara, è possibile scrivere commenti per chiedere maggiori dettagli prima di effettuare il downgrade.
Giorgio,

4
I loop non sono una forma speciale di ricorsione. Guarda la teoria della calcolabilità e, ad esempio, il linguaggio WHILE teorico e il µ-calcolo. Sì, alcune lingue usano i loop come zucchero sintattico per utilizzare effettivamente la ricorsione dietro le quinte, ma possono farlo perché la ricorsione e l'iterazione sono ugualmente espressive , non perché sono uguali.
Polygnome,

-1

Un ciclo while è diverso dalla ricorsione.

Quando viene chiamata una funzione, si verifica quanto segue:

  1. Un frame dello stack viene aggiunto allo stack.

  2. Il puntatore del codice si sposta all'inizio della funzione.

Alla fine di un ciclo while si verifica quanto segue:

  1. Una condizione chiede se qualcosa è vero.

  2. In tal caso, il codice passa a un punto.

In generale, il ciclo while è simile al seguente pseudocodice:

 if (x)

 {

      Jump_to(y);

 }

Soprattutto, ricorsione e loop hanno diverse rappresentazioni del codice assembly e rappresentazioni del codice macchina. Ciò significa che non sono gli stessi. Potrebbero avere gli stessi risultati, ma il diverso codice macchina dimostra che non sono la stessa cosa al 100%.


2
Stai parlando dell'implementazione di una chiamata di procedura e di un ciclo while e, poiché sono implementati in modo diverso, concludi che sono diversi. Tuttavia, concettualmente sono molto simili.
Giorgio,

1
A seconda del compilatore, una chiamata di ricorsione ottimizzata e incorporata potrebbe produrre lo stesso assembly, come loop normale.
hyde,

@hyde ... che è solo un esempio del fatto ben noto che uno può essere espresso attraverso l'altro; non significa che sono identici. Un po 'come la massa e l'energia. Naturalmente si può sostenere che tutti i modi per produrre un output identico sono "uguali". Se il mondo fosse finito, tutti i programmi sarebbero constexpr, alla fine.
Peter - Ripristina Monica il

@Giorgio Nah, è una descrizione logica, non un'implementazione. Sappiamo che le due cose sono equivalenti ; ma l'equivalenza non è identità , perché la domanda (e le risposte) è esattamente come arriviamo al risultato, cioè contengono necessariamente descrizioni algoritmiche (che possono essere espresse in termini di stack, variabili ecc.).
Peter - Ripristina Monica il

1
@ PeterA.Schneider Sì, ma questa risposta indica "La cosa più importante di tutte ... codice assembly diverso", che non è del tutto giusto.
hyde,

-1

Solo l' iterazione è insufficiente per essere generalmente equivalente alla ricorsione, ma l' iterazione con uno stack è generalmente equivalente. Qualsiasi funzione ricorsiva può essere riprogrammata come un ciclo iterativo con uno stack e viceversa. Ciò non significa che sia pratico, tuttavia, e in qualsiasi situazione particolare l'una o l'altra forma può avere chiari vantaggi rispetto all'altra versione.

Non sono sicuro del perché questo sia controverso. La ricorsione e l'iterazione con uno stack sono lo stesso processo computazionale. Sono lo stesso "fenomeno", per così dire.

L'unica cosa a cui riesco a pensare è che quando si considerano questi come "strumenti di programmazione", sono d'accordo che non dovresti pensare a loro come alla stessa cosa. Sono equivalenti "matematicamente" o "computazionalmente" (di nuovo iterazione con una pila , non iterazione in generale), ma ciò non significa che dovresti affrontare i problemi con il pensiero che uno dei due farà. Da un punto di vista dell'implementazione / risoluzione dei problemi, alcuni problemi potrebbero funzionare meglio in un modo o nell'altro, e il tuo compito come programmatore è di decidere correttamente quale è più adatto.

Per chiarire, la risposta alla domanda è un ciclo while intrinsecamente una ricorsione? è un no definito , o almeno "a meno che tu non abbia anche lo stack".

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.