Torre di Hanoi ricorsione

oleg.fresi
Buongiorno! Sto studiando la torre di Hanoi e pur avendo capito il meccanismo che le sta dietro non riesco a capire il funzionamento del codice. Ho difficoltà a capire come funziona la ricorsione in questo problema. Ciò che non capisco dal codice è come fa l'algoritmo a spostare i dischi sul piolo b senza che b appaia nelle cout. Sembra che b sia un parametro che viene passato e non utilizzato, mentre poi compare nella stampa dei passaggi. Potreste farmi vedere passo dopo passo come opera la ricorsione con tre dischi? Grazie in anticipo!
void hanoi(int n, char a, char c, char b)
{
	if (n == 1)
		cout << a << " - " << c << endl;
	else
	{
		hanoi(n - 1, a, b, c);
		cout << a << " - " << c << endl;
		hanoi(n - 1, b, c, a);
	}
}

Risposte
vict85
Seppur l'algoritmo sia ben conosciuto, l'implementazione e, soprattutto, il modo di presentare il risultato non è certo unico. Immagino che la presentazione del risultato sia del tipo "1 - 2" con il significato di "sposto l'elemento più alto della colonna 1 nella colonna 2", giusto? Tra l'altro immagino che ci sia una funzione esterna che chiama la versione giusta a seconda della parità di n.

Comunque non capisco il tuo dubbio: i tre caratteri non hanno lo stesso ordine in tutte le chiamate.

oleg.fresi
a indica il primo piolo da cui si inizia, b indica il piolo d'appoggio e c indica il piolo su cui andranno spostati tutti i dischi alla fine. Non ci sono varianti e casi particolari che dipendano dalla parità di n. Quel che non capisco e l'odine di esecuzione delle chiamate ricorsive. Potresti gentilmente elencarmeli per farmi capire cosa succede ad ogni chiamata, supponendo di vare 3 dischi?

vict85
Se vuoi vedere l'ordine delle chiamate ricorsive non ti servo io, è sufficiente aggiungere un cout come prima riga dalla funzione.

oleg.fresi
Cosa devo inserire però nella cout? Mi serve vedere l'ordine per capire questo: il caso base della ricorsione consiste nel spostare il disco da A a C, ma in realtà viene spostato su B. Ma nel codice mica c'è scritto che si deve spostare prima su B e poi su C. So che forse è una stupidaggine, ma non sto capendo questo.

vict85
L'algoritmo è incompleto, manca infatti il primo passo. Questo comunque è la soluzione minimale per il caso n=3
1)
1  |  |
2  |  |
3  |  |

2)
|  |  |
2  |  |
3  |  1

3)
|  |  |
|  |  |
3  2  1

4)
|  |  |
|  1  |
3  2  |

5)
|  |  |
|  1  |
|  2  3

6)
|  |  |
|  |  |
1  2  3

7)
|  |  |
|  |  2
1  |  3

8)
|  |  1
|  |  2
|  |  3

oleg.fresi
Scusa Vict, quel passo manca? A me come prima mossa stampa da A a C se metto 3 dischi ma se ne metto 4 mi da come prima mossa da A a B. Non capisco come fa l'algoritmo a sfruttare il piolo B e decidere se mettere il primo disco su B o su C. Lo fa dalla seconda chiamata ricorsiva(dopo il cout) ?

axpgn
Beh, su quale piolo metterlo è semplice: se il piolo di arrivo deve essere sempre C allora il primo spostamento deve essere su C se i dischi sono in numero dispari, deve essere su B se sono pari.

oleg.fresi
Ah bene, ma questo è un meccanismo mascherato allora, perchè il codice è molto snello ma di fatto fa molto!

oleg.fresi
Cerchiamo di capire bene cosa succede: la prima esecuzione di hanoi passa a chiamre hanoi stessa perchè abbiamo 3 dischi e non 1, a questo punto hanoi richiamera se stessa con 2 dischi e poi di nuovo se stessa con 1 disco che sposterà da A a C, poi viene eseguita la cout tra le due hanoi e in seguito viene eseguita l'ultima hanoi ricorsivamente. Sono sicuro che non sia questo l'ordine delle chiamate ricorsive per qusto ti chiedevo di scrivermelo. In teoria il disco in cima dovrebbe essere spostato su B perchè nella prima chiamata ricorsiva è il piolo B ad essere quello di fine mentre il piolo C è d'appoggio e provando con 4 dischi in effetti la prima mossa e A-B e non A-C, mentre con tre dischi la prima mossa dovrebbe essere A-B in base a questa istruzione: hanoi(n - 1, a, b, c); ma in realtà è A-C. Vorrei capire bene il perchè di questo.

vict85
Ieri non mi ero focalizzato abbastanza, hai ragione, la logica è lì. Semplicemente la prima azione è dopo varie chiamate ricorsive. Comunque, per capire perché nel caso pari devi partire mandando il più piccolo in 'b' ti suggerisco di cercarti una versione giocabile del gioco di Hanoi (ci saranno giochi javascript o flash gratuiti immagino). Con 4 il numero di mosse non è altissimo.

axpgn
@Zfres
Non conosco il linguaggio in questione (e neppure programmo :D ) però se noti chiama la funzione con $a, c, b$ non $a, b, c$.
Se $n=1$ mette $a$ su $c$, altrimenti richiama la funzione (diminuendo $n$) ma con $b$ e $c$ scambiati quindi, per esempio nel caso $n=2$ la seconda chiamata fa mettere $a$ su $b$.

oleg.fresi
Ok, noi possiamo ragionare così. Ma nel codice c'è scritto che il disco in cima alla torre andrà spostato su B, non specifica se il numero di dischi è pari o dispari. E' una cosa che decide il compilatore? Una volta spostato il primo disco, la funzione a chi viene passata?

oleg.fresi
Si axpgn quello l'ho notato se metto 2 dischi quello in cima lo mette su B. Ma se ne metto 3 o 5 quello in cima lo mette su C. Sto cercando di capire se è una decisione che prende il compilatore in base alla parità dei dischi, perchè nel codice questo non è specificato pe cui a prescindere dalla parità il disco in cima andrebbe messo su B.

vict85
C'è, ma è nascosto. Nella prima chiamata ricorsiva si limita a scambiare b e c. Se tu ripeti due volte questa operazione, la annulli tornando allo stato di partenza. Quindi, siccome questa chiamata si ripete \(n-1\) volte, capirai che per n dispari, b e c non vengono scambiati, mentre per n pari lo sono.

oleg.fresi
Ma come fa a scambiare B e C senza stamparlo? Quando viene chiamata hanoi, con 3 dischi viene richiamata co 2 e poi con 1 e quindi stampa A-C ma nel frattempo come dici tu ha fatto (A-B,C-B, B-C) senza stamparlo?

axpgn
Cosa hai scritto nel titolo? "Ricorsione"
Ecco, riflettici …

Se $n=1$ la prima parte dell'if viene eseguita, altrimenti (cioè se $n>1$) viene chiamata la funzione $n-1$ volte, finché non viene eseguita la prima parte (e quindi vengono scambiati $b$ e $c$, $n-1$ volte.

oleg.fresi
Si su questo ci sono, ma allora dovrebbe anche stamparlo a schermo o no?

axpgn
Perché? Il programma, dopo l'else, non fa che chiamare, RICORSIVAMENTE, la funzione finche $n$ non diventa $1$.
A quel punto esegue la prima parte dell'if e POI torna da dove è stata chiamata.
Quindi se $n=100$, il programma chiama novantanove volte la funzione (e ovviamente scoppia prima :wink:) prima di stampare qualcosa.
Si chiama "ricorsione" per questo.
Comunque segui il consiglio di vict85, simula quello che fa il programma con $n=4$ e osserva ...

oleg.fresi
Si ho osseravato un'animazione, ma una volta che torna dove è stata chiamata cosa succede? Va avanti stampando con il cout, poi chiama di nuvo ricorsivamente se stessa nell'ordine in cui è scritto il codice?

axpgn
Non devi osservare un'animazione ma devi farlo tu, così capisci dove vai a sbattere (eventualmente)!

Rispondi
Per rispondere a questa discussione devi prima effettuare il login.