Torres de Hanói
Mover una torre de discos de un poste a otro sin poner nunca uno grande sobre uno pequeño. El ejemplo perfecto de recursividad: tres líneas lo resuelven en 2ⁿ − 1 movimientos.
nivel intermedioTambién: Hanoi, torres de Hanoi, tower of Hanoi
Visualízalo paso a paso
Cambia los datos, dale a reproducir y sigue cada paso en el dibujo, en la línea de Java que se ejecuta y en sus variables.
Torres de Hanói
Elige cuántos discos: la función recursiva aparta los de encima, mueve el grande y vuelve a poner los de encima, con 2ⁿ − 1 movimientos.
Paso 1
Hay que llevar los 3 discos de A a C, de uno en uno y sin poner nunca un disco sobre otro más pequeño. B sirve de apoyo.
1static void hanoi(int n, char origen, char destino, char auxiliar) {
2 if (n == 0) return;
3 hanoi(n - 1, origen, auxiliar, destino);
4 System.out.println("disco " + n + ": " + origen + " → " + destino);
5 hanoi(n - 1, auxiliar, destino, origen);
6}Variables
- n
- 3
- movimientos
- 0 de 7
Atajos con el foco dentro del visualizador: ← → paso a paso, Espacio reproducir o pausar, Inicio/Fin ir al principio o al final.
La idea
Hay tres postes y n discos de tamaños distintos apilados en el primero, del más grande abajo al más pequeño arriba. Hay que llevarlos todos al tercero moviendo un disco cada vez y sin poner nunca un disco sobre otro más pequeño. Con 3 discos se puede pensar a mano; con 8 ya cuesta, y aun así la solución cabe en tres líneas.
La clave es mirar solo el disco más grande. Para moverlo de A a C, todos los demás tienen que estar fuera de en medio, en B. Así que: primero se llevan los n − 1 de encima de A a B (usando C de apoyo), después se mueve el grande de A a C, y por último se llevan los n − 1 de B a C (usando A de apoyo). Mover n − 1 discos es el mismo problema con uno menos: se resuelve con la misma función. El caso base, 0 discos, no hace nada.
Es el ejemplo perfecto del «salto de fe» de la recursividad: no hace falta imaginar los cientos de movimientos, basta con confiar en que la llamada con n − 1 discos hace bien su trabajo. Cada llamada solo decide su parte, y la pila de llamadas guarda en qué punto de cada nivel se iba.
El número de movimientos cumple T(n) = 2 · T(n − 1) + 1, que da 2ⁿ − 1, y se puede demostrar que no se puede hacer con menos. Crece muy deprisa: la leyenda de los 64 discos de oro necesitaría más de 18 trillones de movimientos. También existe una solución sin recursividad: el disco pequeño se mueve en los turnos impares, siempre en el mismo sentido, y en los pares se hace el único movimiento posible que no lo toca.
Cuándo usarlo
- Para aprender y explicar recursividad: el caso base, el paso recursivo y la pila de llamadas se ven con claridad.
- Como modelo de problemas que se resuelven reduciendo el tamaño en uno y combinando: muchos algoritmos de divide y vencerás siguen el mismo esquema.
- Para estudiar el crecimiento exponencial con un ejemplo que se puede contar a mano.
- En pruebas de rendimiento de la recursividad y de la pila de un lenguaje.
Cuándo no
- Para simular con muchos discos: con 30 ya son más de mil millones de movimientos; si solo interesa un movimiento concreto, se calcula directamente.
- Como patrón para problemas que no se reducen a copias más pequeñas de sí mismos: no todo problema recursivo se parece a Hanói.
- Con más de tres postes: el problema cambia por completo (el de Reve, con cuatro postes, tiene otra solución).
Paso a paso
- Caso base. Si no hay discos que mover (n == 0), no se hace nada.
- Apartar. Llevar los n − 1 discos de encima del origen al poste auxiliar, con la misma función.
- Mover el grande. Mover el disco n, que ya está solo, del origen al destino.
- Volver a poner. Llevar los n − 1 discos del auxiliar al destino, encima del grande, con la misma función.
El código
Hanói recursivo con contador
Los 7 movimientos de 3 discos y la cuenta para 10 y 16 discos, que coincide con 2ⁿ − 1.
1public class Main {
2 static int movimientos = 0;
3
4 static void hanoi(int n, char origen, char destino, char auxiliar, boolean escribir) {
5 if (n == 0) return;
6 hanoi(n - 1, origen, auxiliar, destino, escribir); // aparta los n − 1 de encima
7 movimientos++;
8 if (escribir) System.out.println(movimientos + ". disco " + n + ": " + origen + " → " + destino);
9 hanoi(n - 1, auxiliar, destino, origen, escribir); // y los vuelve a poner encima
10 }
11
12 public static void main(String[] args) {
13 hanoi(3, 'A', 'C', 'B', true);
14 for (int n : new int[]{10, 16}) {
15 movimientos = 0;
16 hanoi(n, 'A', 'C', 'B', false);
17 System.out.println(n + " discos: " + movimientos + " movimientos (2^" + n + " − 1 = " + ((1 << n) - 1) + ")");
18 }
19 }
20}movimientos = 0
def hanoi(n, origen, destino, auxiliar, escribir):
global movimientos
if n == 0:
return
hanoi(n - 1, origen, auxiliar, destino, escribir) # aparta los n − 1 de encima
movimientos += 1
if escribir:
print(f"{movimientos}. disco {n}: {origen} → {destino}")
hanoi(n - 1, auxiliar, destino, origen, escribir) # y los vuelve a poner encima
hanoi(3, "A", "C", "B", True)
for n in (10, 16):
movimientos = 0
hanoi(n, "A", "C", "B", False)
print(f"{n} discos: {movimientos} movimientos (2^{n} − 1 = {(1 << n) - 1})")let movimientos = 0;
function hanoi(n, origen, destino, auxiliar, escribir) {
if (n === 0) return;
hanoi(n - 1, origen, auxiliar, destino, escribir); // aparta los n − 1 de encima
movimientos++;
if (escribir) console.log(`${movimientos}. disco ${n}: ${origen} → ${destino}`);
hanoi(n - 1, auxiliar, destino, origen, escribir); // y los vuelve a poner encima
}
hanoi(3, "A", "C", "B", true);
for (const n of [10, 16]) {
movimientos = 0;
hanoi(n, "A", "C", "B", false);
console.log(`${n} discos: ${movimientos} movimientos (2^${n} − 1 = ${(1 << n) - 1})`);
}using System;
class Program {
static int movimientos = 0;
static void Hanoi(int n, char origen, char destino, char auxiliar, bool escribir) {
if (n == 0) return;
Hanoi(n - 1, origen, auxiliar, destino, escribir); // aparta los n − 1 de encima
movimientos++;
if (escribir) Console.WriteLine(quot;{movimientos}. disco {n}: {origen} → {destino}");
Hanoi(n - 1, auxiliar, destino, origen, escribir); // y los vuelve a poner encima
}
static void Main() {
Hanoi(3, 'A', 'C', 'B', true);
foreach (int n in new[] { 10, 16 }) {
movimientos = 0;
Hanoi(n, 'A', 'C', 'B', false);
Console.WriteLine(quot;{n} discos: {movimientos} movimientos (2^{n} − 1 = {(1 << n) - 1})");
}
}
}<?php
$movimientos = 0;
function hanoi(int $n, string $origen, string $destino, string $auxiliar, bool $escribir): void {
global $movimientos;
if ($n == 0) return;
hanoi($n - 1, $origen, $auxiliar, $destino, $escribir); // aparta los n − 1 de encima
$movimientos++;
if ($escribir) echo "$movimientos. disco $n: $origen → $destino\n";
hanoi($n - 1, $auxiliar, $destino, $origen, $escribir); // y los vuelve a poner encima
}
hanoi(3, "A", "C", "B", true);
foreach ([10, 16] as $n) {
$movimientos = 0;
hanoi($n, "A", "C", "B", false);
echo "$n discos: $movimientos movimientos (2^$n − 1 = " . ((1 << $n) - 1) . ")\n";
}Salida al ejecutarlo (la misma en los 5 lenguajes)
1. disco 1: A → C 2. disco 2: A → B 3. disco 1: C → B 4. disco 3: A → C 5. disco 1: B → A 6. disco 2: B → C 7. disco 1: A → C 10 discos: 1023 movimientos (2^10 − 1 = 1023) 16 discos: 65535 movimientos (2^16 − 1 = 65535)
Hanói sin recursividad
La versión iterativa con tres pilas: el disco 1 da vueltas en los movimientos impares y en los pares solo hay una jugada posible. Hace exactamente los mismos movimientos que la recursiva.
1import java.util.*;
2
3public class Main {
4 /** Sin recursividad: en los movimientos impares el disco 1 avanza en círculo (A→C→B si n es impar,
5 A→B→C si es par); en los pares se hace el único movimiento posible que no lo toca. */
6 static List<String> iterativo(int n) {
7 List<Deque<Integer>> postes = List.of(new ArrayDeque<>(), new ArrayDeque<>(), new ArrayDeque<>());
8 for (int d = n; d >= 1; d--) postes.get(0).push(d);
9 int paso = n % 2 == 0 ? 1 : 2, chico = 0; // chico: el poste donde está el disco 1
10 List<String> movs = new ArrayList<>();
11 for (int k = 1; k < (1 << n); k++) {
12 int de, a;
13 if (k % 2 == 1) {
14 de = chico;
15 a = (chico + paso) % 3;
16 chico = a;
17 } else {
18 int x = (chico + 1) % 3, y = (chico + 2) % 3;
19 boolean deX = !postes.get(x).isEmpty() && (postes.get(y).isEmpty() || postes.get(x).peek() < postes.get(y).peek());
20 de = deX ? x : y;
21 a = deX ? y : x;
22 }
23 int disco = postes.get(de).pop();
24 postes.get(a).push(disco);
25 movs.add("disco " + disco + ": " + "ABC".charAt(de) + " → " + "ABC".charAt(a));
26 }
27 return movs;
28 }
29
30 static void recursivo(int n, int o, int d, int x, List<String> movs) {
31 if (n == 0) return;
32 recursivo(n - 1, o, x, d, movs);
33 movs.add("disco " + n + ": " + "ABC".charAt(o) + " → " + "ABC".charAt(d));
34 recursivo(n - 1, x, d, o, movs);
35 }
36
37 public static void main(String[] args) {
38 List<String> it = iterativo(3);
39 for (int i = 0; i < it.size(); i++) System.out.println((i + 1) + ". " + it.get(i));
40 boolean iguales = true;
41 for (int n = 1; n <= 12; n++) {
42 List<String> r = new ArrayList<>();
43 recursivo(n, 0, 2, 1, r);
44 if (!r.equals(iterativo(n))) iguales = false;
45 }
46 System.out.println("Mismos movimientos que la versión recursiva para n = 1..12: " + (iguales ? "sí" : "no"));
47 }
48}def iterativo(n):
"""Sin recursividad: en los movimientos impares el disco 1 avanza en círculo (A→C→B si n es impar,
A→B→C si es par); en los pares se hace el único movimiento posible que no lo toca."""
postes = [list(range(n, 0, -1)), [], []] # el final de cada lista es el disco de arriba
paso, chico = (1 if n % 2 == 0 else 2), 0 # chico: el poste donde está el disco 1
movs = []
for k in range(1, 1 << n):
if k % 2 == 1:
de, a = chico, (chico + paso) % 3
chico = a
else:
x, y = (chico + 1) % 3, (chico + 2) % 3
de_x = bool(postes[x]) and (not postes[y] or postes[x][-1] < postes[y][-1])
de, a = (x, y) if de_x else (y, x)
disco = postes[de].pop()
postes[a].append(disco)
movs.append(f"disco {disco}: {'ABC'[de]} → {'ABC'[a]}")
return movs
def recursivo(n, o, d, x, movs):
if n == 0:
return
recursivo(n - 1, o, x, d, movs)
movs.append(f"disco {n}: {'ABC'[o]} → {'ABC'[d]}")
recursivo(n - 1, x, d, o, movs)
for i, m in enumerate(iterativo(3), 1):
print(f"{i}. {m}")
iguales = True
for n in range(1, 13):
r = []
recursivo(n, 0, 2, 1, r)
if r != iterativo(n):
iguales = False
print("Mismos movimientos que la versión recursiva para n = 1..12: " + ("sí" if iguales else "no"))/** Sin recursividad: en los movimientos impares el disco 1 avanza en círculo (A→C→B si n es impar,
A→B→C si es par); en los pares se hace el único movimiento posible que no lo toca. */
function iterativo(n) {
const postes = [[], [], []]; // el final de cada array es el disco de arriba
for (let d = n; d >= 1; d--) postes[0].push(d);
const paso = n % 2 === 0 ? 1 : 2;
let chico = 0; // chico: el poste donde está el disco 1
const movs = [];
for (let k = 1; k < (1 << n); k++) {
let de, a;
if (k % 2 === 1) {
de = chico;
a = (chico + paso) % 3;
chico = a;
} else {
const x = (chico + 1) % 3, y = (chico + 2) % 3;
const deX = postes[x].length > 0 && (postes[y].length === 0 || postes[x].at(-1) < postes[y].at(-1));
de = deX ? x : y;
a = deX ? y : x;
}
const disco = postes[de].pop();
postes[a].push(disco);
movs.push(`disco ${disco}: ${"ABC"[de]} → ${"ABC"[a]}`);
}
return movs;
}
function recursivo(n, o, d, x, movs) {
if (n === 0) return;
recursivo(n - 1, o, x, d, movs);
movs.push(`disco ${n}: ${"ABC"[o]} → ${"ABC"[d]}`);
recursivo(n - 1, x, d, o, movs);
}
iterativo(3).forEach((m, i) => console.log(`${i + 1}. ${m}`));
let iguales = true;
for (let n = 1; n <= 12; n++) {
const r = [];
recursivo(n, 0, 2, 1, r);
if (r.join("|") !== iterativo(n).join("|")) iguales = false;
}
console.log("Mismos movimientos que la versión recursiva para n = 1..12: " + (iguales ? "sí" : "no"));using System;
using System.Collections.Generic;
using System.Linq;
class Program {
/// Sin recursividad: en los movimientos impares el disco 1 avanza en círculo (A→C→B si n es impar,
/// A→B→C si es par); en los pares se hace el único movimiento posible que no lo toca.
static List<string> Iterativo(int n) {
var postes = new List<Stack<int>> { new(), new(), new() };
for (int d = n; d >= 1; d--) postes[0].Push(d);
int paso = n % 2 == 0 ? 1 : 2, chico = 0; // chico: el poste donde está el disco 1
var movs = new List<string>();
for (int k = 1; k < (1 << n); k++) {
int de, a;
if (k % 2 == 1) {
de = chico;
a = (chico + paso) % 3;
chico = a;
} else {
int x = (chico + 1) % 3, y = (chico + 2) % 3;
bool deX = postes[x].Count > 0 && (postes[y].Count == 0 || postes[x].Peek() < postes[y].Peek());
de = deX ? x : y;
a = deX ? y : x;
}
int disco = postes[de].Pop();
postes[a].Push(disco);
movs.Add(quot;disco {disco}: {"ABC"[de]} → {"ABC"[a]}");
}
return movs;
}
static void Recursivo(int n, int o, int d, int x, List<string> movs) {
if (n == 0) return;
Recursivo(n - 1, o, x, d, movs);
movs.Add(quot;disco {n}: {"ABC"[o]} → {"ABC"[d]}");
Recursivo(n - 1, x, d, o, movs);
}
static void Main() {
var it = Iterativo(3);
for (int i = 0; i < it.Count; i++) Console.WriteLine(quot;{i + 1}. {it[i]}");
bool iguales = true;
for (int n = 1; n <= 12; n++) {
var r = new List<string>();
Recursivo(n, 0, 2, 1, r);
if (!r.SequenceEqual(Iterativo(n))) iguales = false;
}
Console.WriteLine("Mismos movimientos que la versión recursiva para n = 1..12: " + (iguales ? "sí" : "no"));
}
}<?php
/** Sin recursividad: en los movimientos impares el disco 1 avanza en círculo (A→C→B si n es impar,
A→B→C si es par); en los pares se hace el único movimiento posible que no lo toca. */
function iterativo(int $n): array {
$postes = [range($n, 1), [], []]; // el final de cada array es el disco de arriba
$paso = $n % 2 == 0 ? 1 : 2;
$chico = 0; // chico: el poste donde está el disco 1
$movs = [];
for ($k = 1; $k < (1 << $n); $k++) {
if ($k % 2 == 1) {
$de = $chico;
$a = ($chico + $paso) % 3;
$chico = $a;
} else {
$x = ($chico + 1) % 3;
$y = ($chico + 2) % 3;
$deX = count($postes[$x]) > 0 && (count($postes[$y]) == 0 || $postes[$x][count($postes[$x]) - 1] < $postes[$y][count($postes[$y]) - 1]);
$de = $deX ? $x : $y;
$a = $deX ? $y : $x;
}
$disco = array_pop($postes[$de]);
$postes[$a][] = $disco;
$movs[] = "disco $disco: " . "ABC"[$de] . " → " . "ABC"[$a];
}
return $movs;
}
function recursivo(int $n, int $o, int $d, int $x, array &$movs): void {
if ($n == 0) return;
recursivo($n - 1, $o, $x, $d, $movs);
$movs[] = "disco $n: " . "ABC"[$o] . " → " . "ABC"[$d];
recursivo($n - 1, $x, $d, $o, $movs);
}
foreach (iterativo(3) as $i => $m) echo ($i + 1) . ". $m\n";
$iguales = true;
for ($n = 1; $n <= 12; $n++) {
$r = [];
recursivo($n, 0, 2, 1, $r);
if ($r !== iterativo($n)) $iguales = false;
}
echo "Mismos movimientos que la versión recursiva para n = 1..12: " . ($iguales ? "sí" : "no") . "\n";Salida al ejecutarlo (la misma en los 5 lenguajes)
1. disco 1: A → C 2. disco 2: A → B 3. disco 1: C → B 4. disco 3: A → C 5. disco 1: B → A 6. disco 2: B → C 7. disco 1: A → C Mismos movimientos que la versión recursiva para n = 1..12: sí
Traza: Los 7 movimientos de 3 discos (de A a C)
| Movimiento | Disco | De | A | Postes después (de abajo arriba) |
|---|---|---|---|---|
| 1 | 1 | A | C | A: 3 2 · B: — · C: 1 |
| 2 | 2 | A | B | A: 3 · B: 2 · C: 1 |
| 3 | 1 | C | B | A: 3 · B: 2 1 · C: — |
| 4 | 3 | A | C | A: — · B: 2 1 · C: 3 |
| 5 | 1 | B | A | A: 1 · B: 2 · C: 3 |
| 6 | 2 | B | C | A: 1 · B: — · C: 3 2 |
| 7 | 1 | A | C | A: — · B: — · C: 3 2 1 |
El disco 3 se mueve justo en el centro (movimiento 4): antes, los otros dos se apartan a B; después, vuelven encima.
Complejidad
| Medida | Coste |
|---|---|
| Movimientos | 2ⁿ − 1 (el mínimo posible) |
| Tiempo | O(2ⁿ) |
| Memoria de la versión recursiva | O(n): la profundidad de la pila de llamadas |
| Calcular solo el movimiento k | O(n) |
Cada disco más duplica el trabajo: 10 discos son 1.023 movimientos y 20 discos, más de un millón.
- Mejor caso: O(2ⁿ)
- Caso medio: O(2ⁿ)
- Peor caso: O(2ⁿ)
Siempre 2ⁿ − 1 movimientos, y es el mínimo posible: no hay forma más rápida de resolverlo. Las curvas grises son las demás clases, para comparar.
En la práctica
- Es el ejercicio de recursividad de casi todos los cursos y libros de programación desde hace décadas.
- Las rotaciones de copias de seguridad «torre de Hanói» usan el mismo patrón para decidir qué cinta se reutiliza cada día.
- Se usa en psicología y neurología como prueba de planificación (la torre de Londres es una variante).
- El número de movimientos 2ⁿ − 1 aparece en otros problemas: el código Gray, los subconjuntos de un conjunto…
Errores típicos
- Olvidar el caso base o ponerlo en n == 1 sin tratar n == 0: recursividad infinita con 0 discos.
- Intercambiar mal los postes en las llamadas: en la primera, el auxiliar hace de destino; en la segunda, de origen.
- Intentar «ver» todos los movimientos en vez de confiar en que la llamada con n − 1 está bien.
- Usar
intpara contar movimientos con muchos discos: con 31 discos ya se pasa; hace faltalong. - Simular millones de movimientos cuando solo se pide uno: se puede calcular directamente.
Ejercicios
Cada ejercicio se corrige solo con sus pruebas (algunas ocultas). Escribe tu solución en el editor y pulsa Ejecutar o Comprobar; la solución explicada está debajo, por si te atascas.
1. Simulador de Hanói
La entrada dice cuántos discos hay, en qué poste están y a cuál hay que llevarlos. El método mover ya mueve un disco, comprueba las reglas (lanza una excepción si se rompen) y escribe cómo quedan los postes. Completa hanoi para que resuelva el problema llamando a mover.
- Entrada: una línea
discos origen destino, como3 A C(de 1 a 8 discos; postes A, B y C). - Salida: una línea por movimiento,
1. disco 1: A → C | A: 3 2 B: C: 1(cada poste de abajo arriba), y al finalHecho en 7 movimientos. - Si se rompe una regla:
Movimiento ilegal: …. Entrada incorrecta:Entrada no válida: «…» (se espera: discos origen destino, como «3 A C»).
Ejemplo
3 A C
1. disco 1: A → C | A: 3 2 B: C: 1 2. disco 2: A → B | A: 3 B: 2 C: 1 3. disco 1: C → B | A: 3 B: 2 1 C: 4. disco 3: A → C | A: B: 2 1 C: 3 5. disco 1: B → A | A: 1 B: 2 C: 3 6. disco 2: B → C | A: 1 B: C: 3 2 7. disco 1: A → C | A: B: C: 3 2 1 Hecho en 7 movimientos
Ver la solución explicada
1import java.util.*;
2
3public class Main {
4 static final List<Deque<Integer>> postes = List.of(new ArrayDeque<>(), new ArrayDeque<>(), new ArrayDeque<>());
5 static int movimientos = 0;
6
7 /** Mueve el disco de arriba de un poste a otro comprobando las reglas, y escribe cómo quedan los postes. */
8 static void mover(int de, int a) {
9 Deque<Integer> origen = postes.get(de), destino = postes.get(a);
10 if (origen.isEmpty()) throw new IllegalStateException("el poste " + "ABC".charAt(de) + " está vacío");
11 if (!destino.isEmpty() && destino.peek() < origen.peek())
12 throw new IllegalStateException("el disco " + origen.peek() + " no puede ir sobre el " + destino.peek());
13 destino.push(origen.pop());
14 movimientos++;
15 StringBuilder sb = new StringBuilder(movimientos + ". disco " + destino.peek() + ": " + "ABC".charAt(de) + " → " + "ABC".charAt(a) + " |");
16 for (int t = 0; t < 3; t++) {
17 List<Integer> discos = new ArrayList<>(postes.get(t));
18 Collections.reverse(discos); // de abajo arriba
19 sb.append(" ").append("ABC".charAt(t)).append(":");
20 for (int d : discos) sb.append(" ").append(d);
21 }
22 System.out.println(sb);
23 }
24
25 /** Lleva n discos del poste o al d usando x de apoyo (los postes son 0, 1 y 2), llamando a mover. */
26 static void hanoi(int n, int o, int d, int x) {
27 if (n == 0) return;
28 hanoi(n - 1, o, x, d);
29 mover(o, d);
30 hanoi(n - 1, x, d, o);
31 }
32
33 public static void main(String[] args) {
34 Scanner sc = new Scanner(System.in);
35 String linea = sc.hasNextLine() ? sc.nextLine().trim() : "";
36 String[] p = linea.split("\\s+");
37 if (p.length != 3 || !p[0].matches("[1-8]") || !p[1].matches("[ABC]") || !p[2].matches("[ABC]") || p[1].equals(p[2])) {
38 System.out.println("Entrada no válida: «" + linea + "» (se espera: discos origen destino, como «3 A C»)");
39 return;
40 }
41 int n = Integer.parseInt(p[0]), o = p[1].charAt(0) - 'A', d = p[2].charAt(0) - 'A';
42 for (int k = n; k >= 1; k--) postes.get(o).push(k);
43 try {
44 hanoi(n, o, d, 3 - o - d);
45 System.out.println("Hecho en " + movimientos + " movimientos");
46 } catch (IllegalStateException e) {
47 System.out.println("Movimiento ilegal: " + e.getMessage());
48 }
49 }
50}La función no comprueba nada: confía en que, si cada llamada respeta el esquema, ningún movimiento romperá las reglas. mover lo verifica de todos modos, y nunca salta.
Con 8 discos son 255 movimientos, 2⁸ − 1: compruébalo en la última línea.
2. El movimiento número k
Con n discos que van de A a C, ¿qué disco se mueve en el movimiento k, y de dónde a dónde? Con 60 discos hay más de un trillón de movimientos: no se puede simular. Pero la estructura recursiva lo permite calcular directamente. El main lee las consultas y valida los rangos: completa movimiento.
- Entrada: una consulta por línea,
n k(n de 1 a 62 discos, k desde 1). - Salida:
Movimiento 4 de 7: disco 3 de A a C. - Errores:
Línea no válida: «…»,Número de discos fuera de rango (de 1 a 62): 70,Con 3 discos solo hay movimientos del 1 al 7.
Ejemplo
3 1 3 4 3 7
Movimiento 1 de 7: disco 1 de A a C Movimiento 4 de 7: disco 3 de A a C Movimiento 7 de 7: disco 1 de A a C
Ver la solución explicada
1import java.util.*;
2
3public class Main {
4 /** Qué pasa en el movimiento k (desde 1) al llevar n discos de o a d con x de apoyo: {disco, de, a}.
5 Sin simular: los 2^(n−1) − 1 primeros apartan n − 1 discos, el siguiente mueve el disco n y el
6 resto vuelve a poner los n − 1 encima. */
7 static int[] movimiento(int n, long k, int o, int d, int x) {
8 long mitad = 1L << (n - 1);
9 if (k == mitad) return new int[]{n, o, d};
10 if (k < mitad) return movimiento(n - 1, k, o, x, d);
11 return movimiento(n - 1, k - mitad, x, d, o);
12 }
13
14 public static void main(String[] args) {
15 Scanner sc = new Scanner(System.in);
16 while (sc.hasNextLine()) {
17 String linea = sc.nextLine().trim();
18 if (linea.isEmpty()) continue;
19 String[] p = linea.split("\\s+");
20 if (p.length != 2 || !p[0].matches("\\d{1,2}") || !p[1].matches("\\d{1,19}")) {
21 System.out.println("Línea no válida: «" + linea + "»");
22 continue;
23 }
24 int n = Integer.parseInt(p[0]);
25 if (n < 1 || n > 62) {
26 System.out.println("Número de discos fuera de rango (de 1 a 62): " + n);
27 continue;
28 }
29 long total = (1L << n) - 1, k;
30 try {
31 k = Long.parseLong(p[1]);
32 } catch (NumberFormatException e) {
33 k = -1;
34 }
35 if (k < 1 || k > total) {
36 System.out.println("Con " + n + " discos solo hay movimientos del 1 al " + total);
37 continue;
38 }
39 int[] m = movimiento(n, k, 0, 2, 1);
40 System.out.println("Movimiento " + k + " de " + total + ": disco " + m[0] + " de " + "ABC".charAt(m[1]) + " a " + "ABC".charAt(m[2]));
41 }
42 }
43}Cada llamada descarta la mitad de los movimientos, como una búsqueda binaria: como mucho n llamadas, en vez de 2ⁿ − 1 movimientos simulados.
Curiosidad: el disco que se mueve en el paso k es uno más que el número de ceros al final de k en binario (Long.numberOfTrailingZeros(k) + 1).
Test
Test: Torres de Hanói
0/5 respondidas · 0 aciertosElige una respuesta en cada pregunta: verás al momento si es correcta y por qué. Con un 80 % de aciertos se da por superada.
1.¿Cuántos movimientos hacen falta, como mínimo, para 5 discos?
2.En hanoi(n, A, C, B), ¿qué hace la primera llamada recursiva?
3.¿Qué memoria usa la versión recursiva con n discos?
4.¿Qué disco se mueve en el movimiento central (el 2^(n−1))?
5.¿Qué recurrencia cumple el número de movimientos?