Recorrido en anchura (BFS)
Recorre un grafo por capas: primero los vecinos del origen, luego los vecinos de esos… Con una cola, encuentra el camino con menos aristas entre dos nodos.
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.
Recorrido en anchura (BFS)
Escribe las aristas de un grafo y el nodo de salida: BFS lo recorre por capas, de lo más cercano a lo más lejano, con una cola.
- descubierto, en la cola
Paso 1
Se marca A como visto y entra en la cola. La cola es la clave de BFS: el primero que entra es el primero que sale, así que los nodos salen por orden de distancia a A (d = número de aristas).
1static List<String> bfs(Map<String, List<String>> g, String origen) {
2 List<String> orden = new ArrayList<>();
3 Set<String> vistos = new HashSet<>(List.of(origen));
4 Deque<String> cola = new ArrayDeque<>(List.of(origen)); // cola = [A], orden = —
5 while (!cola.isEmpty()) {
6 String u = cola.poll();
7 orden.add(u);
8 for (String v : g.get(u))
9 if (vistos.add(v)) cola.add(v);
10 }
11 return orden;
12}Variables
- cola
- [A]
- orden
- —
Atajos con el foco dentro del visualizador: ← → paso a paso, Espacio reproducir o pausar, Inicio/Fin ir al principio o al final.
La idea
Un grafo es un conjunto de nodos unidos por aristas: ciudades y carreteras, personas que se siguen, casillas de un tablero. En el código casi siempre se guarda como una lista de adyacencia: para cada nodo, la lista de sus vecinos (Map<String, List<String>>). Si las aristas tienen dirección (A sigue a B, pero B no a A) el grafo es dirigido; si no, cada arista se apunta en los dos sentidos.
El recorrido en anchura (BFS, *breadth-first search*) explora desde un nodo de origen como una mancha de aceite: primero el origen, luego todos sus vecinos, luego los vecinos de los vecinos… Para conseguir ese orden usa una cola: se saca el primero, se miran sus vecinos y los que no se habían visto se meten al final. Como lo que entra antes sale antes, los nodos salen por orden de distancia.
Esa es su gran propiedad: la primera vez que BFS descubre un nodo, lo hace por un camino con el mínimo de aristas. Si al descubrir cada nodo se apunta desde cuál se llegó (su padre), al final basta con seguir los padres hacia atrás para reconstruir el camino más corto. Por eso BFS es el algoritmo del camino mínimo cuando todas las aristas «cuestan» lo mismo: saltos en una red social, movimientos en un laberinto o en un tablero.
El detalle que más errores provoca: un nodo se marca como visto al meterlo en la cola, no al sacarlo. Si se marca al sacarlo, el mismo nodo puede entrar varias veces (una por cada vecino que lo descubra) y el recorrido hace trabajo de más. Y sin el conjunto de vistos, en cuanto el grafo tiene un ciclo, BFS no termina nunca.
Cuándo usarlo
- El camino más corto en número de pasos: saltos en una red, movimientos en un laberinto, en un tablero o en un puzle (cada estado del puzle es un nodo).
- Recorrer por niveles: los amigos de tus amigos hasta el grado 3, un árbol planta a planta.
- Saber qué se alcanza desde un nodo (y a qué distancia) o si dos nodos están conectados.
- Propagar algo desde un punto: el relleno de una zona, la difusión en una red, un rastreador web.
Cuándo no
- Si las aristas tienen pesos distintos (kilómetros, minutos, euros): el camino con menos aristas no es el más corto; hace falta Dijkstra.
- Si el grafo es enorme y muy ancho y solo quieres saber si hay camino: la cola de BFS puede llegar a tener una capa entera; DFS gasta menos memoria.
- Para detectar ciclos en grafos dirigidos u ordenar dependencias: DFS o la ordenación topológica encajan mejor.
Paso a paso
- Preparar. Marca el origen como visto, apunta su distancia (0) y mételo en la cola.
- Sacar el primero. Mientras la cola no esté vacía, saca el nodo que lleva más tiempo esperando.
- Descubrir vecinos. Por cada vecino que aún no se haya visto: márcalo, su distancia es la del actual + 1, apunta que su padre es el actual y mételo al final de la cola.
- Reconstruir el camino. Para ir del origen a un nodo, sigue sus padres hacia atrás hasta el origen y da la vuelta a la lista.
El código
BFS: distancias y camino más corto
El grafo del visualizador: BFS desde A apuntando la distancia y el padre de cada nodo; el camino de A a G se reconstruye hacia atrás.
1import java.util.*;
2
3public class Main {
4 static final Map<String, List<String>> grafo = new LinkedHashMap<>();
5
6 static void arista(String a, String b) { // no dirigida: se apunta en los dos sentidos
7 grafo.computeIfAbsent(a, k -> new ArrayList<>()).add(b);
8 grafo.computeIfAbsent(b, k -> new ArrayList<>()).add(a);
9 }
10
11 public static void main(String[] args) {
12 String[][] aristas = {{"A", "B"}, {"A", "C"}, {"B", "D"}, {"C", "D"}, {"C", "E"}, {"D", "F"}, {"E", "F"}, {"F", "G"}};
13 for (String[] e : aristas) arista(e[0], e[1]);
14
15 Map<String, Integer> dist = new HashMap<>();
16 Map<String, String> padre = new HashMap<>();
17 Deque<String> cola = new ArrayDeque<>();
18 List<String> orden = new ArrayList<>();
19 dist.put("A", 0);
20 cola.add("A");
21 while (!cola.isEmpty()) {
22 String u = cola.poll();
23 orden.add(u);
24 for (String v : grafo.get(u)) {
25 if (!dist.containsKey(v)) { // la primera vez que se ve: por el camino más corto
26 dist.put(v, dist.get(u) + 1);
27 padre.put(v, u);
28 cola.add(v);
29 }
30 }
31 }
32 System.out.println("Orden de visita: " + String.join(" ", orden));
33 for (String n : orden) System.out.println(" dist(" + n + ") = " + dist.get(n));
34
35 LinkedList<String> camino = new LinkedList<>();
36 for (String n = "G"; n != null; n = padre.get(n)) camino.addFirst(n); // de G hacia atrás
37 System.out.println("Camino más corto de A a G: " + String.join(" → ", camino));
38 }
39}from collections import deque
grafo = {}
def arista(a, b): # no dirigida: se apunta en los dos sentidos
grafo.setdefault(a, []).append(b)
grafo.setdefault(b, []).append(a)
for a, b in [("A", "B"), ("A", "C"), ("B", "D"), ("C", "D"), ("C", "E"), ("D", "F"), ("E", "F"), ("F", "G")]:
arista(a, b)
dist = {"A": 0}
padre = {}
cola = deque(["A"])
orden = []
while cola:
u = cola.popleft()
orden.append(u)
for v in grafo[u]:
if v not in dist: # la primera vez que se ve: por el camino más corto
dist[v] = dist[u] + 1
padre[v] = u
cola.append(v)
print("Orden de visita:", " ".join(orden))
for n in orden:
print(f" dist({n}) = {dist[n]}")
camino = []
n = "G"
while n is not None: # de G hacia atrás
camino.insert(0, n)
n = padre.get(n)
print("Camino más corto de A a G:", " → ".join(camino))const grafo = new Map();
function arista(a, b) { // no dirigida: se apunta en los dos sentidos
if (!grafo.has(a)) grafo.set(a, []);
if (!grafo.has(b)) grafo.set(b, []);
grafo.get(a).push(b);
grafo.get(b).push(a);
}
for (const [a, b] of [["A", "B"], ["A", "C"], ["B", "D"], ["C", "D"], ["C", "E"], ["D", "F"], ["E", "F"], ["F", "G"]]) arista(a, b);
const dist = new Map([["A", 0]]);
const padre = new Map();
const cola = ["A"];
const orden = [];
while (cola.length > 0) {
const u = cola.shift(); // shift() es O(n): con colas enormes, mejor un índice
orden.push(u);
for (const v of grafo.get(u)) {
if (!dist.has(v)) { // la primera vez que se ve: por el camino más corto
dist.set(v, dist.get(u) + 1);
padre.set(v, u);
cola.push(v);
}
}
}
console.log("Orden de visita: " + orden.join(" "));
for (const n of orden) console.log(` dist(${n}) = ${dist.get(n)}`);
const camino = [];
for (let n = "G"; n !== undefined; n = padre.get(n)) camino.unshift(n); // de G hacia atrás
console.log("Camino más corto de A a G: " + camino.join(" → "));using System;
using System.Collections.Generic;
class Program {
static readonly Dictionary<string, List<string>> grafo = new();
static void Arista(string a, string b) { // no dirigida: se apunta en los dos sentidos
if (!grafo.ContainsKey(a)) grafo[a] = new List<string>();
if (!grafo.ContainsKey(b)) grafo[b] = new List<string>();
grafo[a].Add(b);
grafo[b].Add(a);
}
static void Main() {
foreach (var (a, b) in new[] { ("A", "B"), ("A", "C"), ("B", "D"), ("C", "D"), ("C", "E"), ("D", "F"), ("E", "F"), ("F", "G") }) Arista(a, b);
var dist = new Dictionary<string, int> { ["A"] = 0 };
var padre = new Dictionary<string, string>();
var cola = new Queue<string>();
var orden = new List<string>();
cola.Enqueue("A");
while (cola.Count > 0) {
string u = cola.Dequeue();
orden.Add(u);
foreach (string v in grafo[u]) {
if (!dist.ContainsKey(v)) { // la primera vez que se ve: por el camino más corto
dist[v] = dist[u] + 1;
padre[v] = u;
cola.Enqueue(v);
}
}
}
Console.WriteLine("Orden de visita: " + string.Join(" ", orden));
foreach (string n in orden) Console.WriteLine(quot; dist({n}) = {dist[n]}");
var camino = new List<string>();
for (string? n = "G"; n != null; n = padre.GetValueOrDefault(n)) camino.Insert(0, n); // de G hacia atrás
Console.WriteLine("Camino más corto de A a G: " + string.Join(" → ", camino));
}
}<?php
$grafo = [];
foreach ([["A", "B"], ["A", "C"], ["B", "D"], ["C", "D"], ["C", "E"], ["D", "F"], ["E", "F"], ["F", "G"]] as [$a, $b]) { // no dirigida: se apunta en los dos sentidos
$grafo[$a][] = $b;
$grafo[$b][] = $a;
}
$dist = ["A" => 0];
$padre = [];
$cola = new SplQueue();
$cola->enqueue("A");
$orden = [];
while (!$cola->isEmpty()) {
$u = $cola->dequeue();
$orden[] = $u;
foreach ($grafo[$u] as $v) {
if (!isset($dist[$v])) { // la primera vez que se ve: por el camino más corto
$dist[$v] = $dist[$u] + 1;
$padre[$v] = $u;
$cola->enqueue($v);
}
}
}
echo "Orden de visita: " . implode(" ", $orden) . "\n";
foreach ($orden as $n) echo " dist($n) = {$dist[$n]}\n";
$camino = [];
for ($n = "G"; $n !== null; $n = $padre[$n] ?? null) array_unshift($camino, $n); // de G hacia atrás
echo "Camino más corto de A a G: " . implode(" → ", $camino) . "\n";Salida al ejecutarlo (la misma en los 5 lenguajes)
Orden de visita: A B C D E F G dist(A) = 0 dist(B) = 1 dist(C) = 1 dist(D) = 2 dist(E) = 2 dist(F) = 3 dist(G) = 4 Camino más corto de A a G: A → B → D → F → G
Laberinto: BFS en una cuadrícula
Cada casilla libre es un nodo y sus vecinas (arriba, abajo, izquierda, derecha) son sus aristas: no hace falta construir el grafo. El camino encontrado se dibuja con asteriscos.
1import java.util.*;
2
3public class Main {
4 public static void main(String[] args) {
5 String[] plano = {
6 "S.#.......",
7 ".##.####.#",
8 "....#....#",
9 ".##...##.E",
10 "...#.#....",
11 };
12 int filas = plano.length, cols = plano[0].length();
13 char[][] m = new char[filas][];
14 int ini = 0, fin = 0;
15 for (int f = 0; f < filas; f++) {
16 m[f] = plano[f].toCharArray();
17 for (int c = 0; c < cols; c++) {
18 if (m[f][c] == 'S') ini = f * cols + c; // cada casilla es un nodo: f * cols + c
19 if (m[f][c] == 'E') fin = f * cols + c;
20 }
21 }
22 int[] df = {-1, 1, 0, 0}, dc = {0, 0, -1, 1}; // arriba, abajo, izquierda, derecha
23 int[] previa = new int[filas * cols];
24 Arrays.fill(previa, -2); // -2: sin descubrir
25 previa[ini] = -1;
26 Deque<Integer> cola = new ArrayDeque<>(List.of(ini));
27 while (!cola.isEmpty()) {
28 int u = cola.poll();
29 if (u == fin) break; // la primera vez que se llega es por el camino más corto
30 for (int k = 0; k < 4; k++) {
31 int f = u / cols + df[k], c = u % cols + dc[k];
32 if (f < 0 || f >= filas || c < 0 || c >= cols || m[f][c] == '#' || previa[f * cols + c] != -2) continue;
33 previa[f * cols + c] = u;
34 cola.add(f * cols + c);
35 }
36 }
37 if (previa[fin] == -2) {
38 System.out.println("No hay salida");
39 return;
40 }
41 int pasos = 1;
42 for (int p = previa[fin]; p != ini; p = previa[p]) { // de la salida hacia atrás
43 m[p / cols][p % cols] = '*';
44 pasos++;
45 }
46 System.out.println("Camino más corto: " + pasos + " pasos");
47 for (char[] fila : m) System.out.println(new String(fila));
48 }
49}from collections import deque
plano = [
"S.#.......",
".##.####.#",
"....#....#",
".##...##.E",
"...#.#....",
]
filas, cols = len(plano), len(plano[0])
m = [list(fila) for fila in plano]
ini = fin = 0
for f in range(filas):
for c in range(cols):
if m[f][c] == "S":
ini = f * cols + c # cada casilla es un nodo: f * cols + c
if m[f][c] == "E":
fin = f * cols + c
movimientos = [(-1, 0), (1, 0), (0, -1), (0, 1)] # arriba, abajo, izquierda, derecha
previa = [-2] * (filas * cols) # -2: sin descubrir
previa[ini] = -1
cola = deque([ini])
while cola:
u = cola.popleft()
if u == fin:
break # la primera vez que se llega es por el camino más corto
for df, dc in movimientos:
f, c = u // cols + df, u % cols + dc
if 0 <= f < filas and 0 <= c < cols and m[f][c] != "#" and previa[f * cols + c] == -2:
previa[f * cols + c] = u
cola.append(f * cols + c)
if previa[fin] == -2:
print("No hay salida")
else:
pasos = 1
p = previa[fin]
while p != ini: # de la salida hacia atrás
m[p // cols][p % cols] = "*"
pasos += 1
p = previa[p]
print(f"Camino más corto: {pasos} pasos")
for fila in m:
print("".join(fila))const plano = [
"S.#.......",
".##.####.#",
"....#....#",
".##...##.E",
"...#.#....",
];
const filas = plano.length, cols = plano[0].length;
const m = plano.map((fila) => [...fila]);
let ini = 0, fin = 0;
for (let f = 0; f < filas; f++)
for (let c = 0; c < cols; c++) {
if (m[f][c] === "S") ini = f * cols + c; // cada casilla es un nodo: f * cols + c
if (m[f][c] === "E") fin = f * cols + c;
}
const df = [-1, 1, 0, 0], dc = [0, 0, -1, 1]; // arriba, abajo, izquierda, derecha
const previa = new Array(filas * cols).fill(-2); // -2: sin descubrir
previa[ini] = -1;
const cola = [ini];
for (let i = 0; i < cola.length; i++) { // un índice que avanza en vez de shift()
const u = cola[i];
if (u === fin) break; // la primera vez que se llega es por el camino más corto
for (let k = 0; k < 4; k++) {
const f = Math.floor(u / cols) + df[k], c = (u % cols) + dc[k];
if (f < 0 || f >= filas || c < 0 || c >= cols || m[f][c] === "#" || previa[f * cols + c] !== -2) continue;
previa[f * cols + c] = u;
cola.push(f * cols + c);
}
}
if (previa[fin] === -2) console.log("No hay salida");
else {
let pasos = 1;
for (let p = previa[fin]; p !== ini; p = previa[p]) { // de la salida hacia atrás
m[Math.floor(p / cols)][p % cols] = "*";
pasos++;
}
console.log(`Camino más corto: ${pasos} pasos`);
for (const fila of m) console.log(fila.join(""));
}using System;
using System.Collections.Generic;
class Program {
static void Main() {
string[] plano = {
"S.#.......",
".##.####.#",
"....#....#",
".##...##.E",
"...#.#....",
};
int filas = plano.Length, cols = plano[0].Length;
var m = new char[filas][];
int ini = 0, fin = 0;
for (int f = 0; f < filas; f++) {
m[f] = plano[f].ToCharArray();
for (int c = 0; c < cols; c++) {
if (m[f][c] == 'S') ini = f * cols + c; // cada casilla es un nodo: f * cols + c
if (m[f][c] == 'E') fin = f * cols + c;
}
}
int[] df = { -1, 1, 0, 0 }, dc = { 0, 0, -1, 1 }; // arriba, abajo, izquierda, derecha
var previa = new int[filas * cols];
Array.Fill(previa, -2); // -2: sin descubrir
previa[ini] = -1;
var cola = new Queue<int>();
cola.Enqueue(ini);
while (cola.Count > 0) {
int u = cola.Dequeue();
if (u == fin) break; // la primera vez que se llega es por el camino más corto
for (int k = 0; k < 4; k++) {
int f = u / cols + df[k], c = u % cols + dc[k];
if (f < 0 || f >= filas || c < 0 || c >= cols || m[f][c] == '#' || previa[f * cols + c] != -2) continue;
previa[f * cols + c] = u;
cola.Enqueue(f * cols + c);
}
}
if (previa[fin] == -2) {
Console.WriteLine("No hay salida");
return;
}
int pasos = 1;
for (int p = previa[fin]; p != ini; p = previa[p]) { // de la salida hacia atrás
m[p / cols][p % cols] = '*';
pasos++;
}
Console.WriteLine(quot;Camino más corto: {pasos} pasos");
foreach (var fila in m) Console.WriteLine(new string(fila));
}
}<?php
$plano = [
"S.#.......",
".##.####.#",
"....#....#",
".##...##.E",
"...#.#....",
];
$filas = count($plano);
$cols = strlen($plano[0]);
$m = $plano; // en PHP se puede cambiar un carácter de un string: $s[3] = "*"
$ini = $fin = 0;
for ($f = 0; $f < $filas; $f++)
for ($c = 0; $c < $cols; $c++) {
if ($m[$f][$c] === "S") $ini = $f * $cols + $c; // cada casilla es un nodo: f * cols + c
if ($m[$f][$c] === "E") $fin = $f * $cols + $c;
}
$movimientos = [[-1, 0], [1, 0], [0, -1], [0, 1]]; // arriba, abajo, izquierda, derecha
$previa = array_fill(0, $filas * $cols, -2); // -2: sin descubrir
$previa[$ini] = -1;
$cola = new SplQueue();
$cola->enqueue($ini);
while (!$cola->isEmpty()) {
$u = $cola->dequeue();
if ($u === $fin) break; // la primera vez que se llega es por el camino más corto
foreach ($movimientos as [$df, $dc]) {
$f = intdiv($u, $cols) + $df;
$c = $u % $cols + $dc;
if ($f < 0 || $f >= $filas || $c < 0 || $c >= $cols || $m[$f][$c] === "#" || $previa[$f * $cols + $c] !== -2) continue;
$previa[$f * $cols + $c] = $u;
$cola->enqueue($f * $cols + $c);
}
}
if ($previa[$fin] === -2) {
echo "No hay salida\n";
} else {
$pasos = 1;
for ($p = $previa[$fin]; $p !== $ini; $p = $previa[$p]) { // de la salida hacia atrás
$m[intdiv($p, $cols)][$p % $cols] = "*";
$pasos++;
}
echo "Camino más corto: $pasos pasos\n";
foreach ($m as $fila) echo $fila, "\n";
}Salida al ejecutarlo (la misma en los 5 lenguajes)
Camino más corto: 14 pasos S.#....... *##.####.# ****#****# .##***##*E ...#.#....
Traza: BFS desde A en el grafo del visualizador
| Sale | Vecinos nuevos (entran) | Cola después | Distancia |
|---|---|---|---|
| A | B, C | B C | d(A) = 0 |
| B | D | C D | d(B) = 1 |
| C | E | D E | d(C) = 1 |
| D | F | E F | d(D) = 2 |
| E | — | F | d(E) = 2 |
| F | G | G | d(F) = 3 |
| G | — | vacía | d(G) = 4 |
Los nodos salen de la cola en orden de distancia: primero el 0, luego los dos a distancia 1, luego los de 2… D se descubre desde B y por eso, cuando C lo mira, ya está visto.
Complejidad
| Operación | Lista de adyacencia | Matriz de adyacencia |
|---|---|---|
| Recorrer todo (BFS o DFS) | O(V + E) | O(V²) |
| Memoria del grafo | O(V + E) | O(V²) |
| ¿Son vecinos u y v? | O(grado de u) | O(1) |
| Memoria extra de BFS (cola y vistos) | O(V) | O(V) |
V es el n úmero de nodos y E el de aristas. Los grafos reales suelen ser dispersos (pocas aristas por nodo): por eso la lista de adyacencia es lo habitual.
- Mejor caso: O(n)
- Caso medio: O(n)
- Peor caso: O(n)
En realidad O(V + E): cada nodo entra y sale de la cola una vez y cada arista se mira desde sus dos extremos. Las curvas grises son las demás clases, para comparar.
En la práctica
- Las redes sociales calculan «amigos de amigos» y los grados de separación con BFS limitados a 2 o 3 niveles.
- Los juegos mueven personajes por mapas de casillas con BFS (o con A*, su versión con pesos y estimación).
- Los rastreadores de los buscadores recorren la web por anchura desde unas páginas semilla.
- El recolector de basura de Java marca los objetos vivos recorriendo el grafo de referencias desde las raíces.
- En redes, la difusión (*broadcast*) y el descubrimiento de vecinos avanzan por capas.
Errores típicos
- Marcar como visto al sacar de la cola en vez de al meter: el mismo nodo entra varias veces.
- Olvidar el conjunto de vistos: con un ciclo, el bucle no termina.
- Usar una pila en lugar de una cola (
popen vez depoll): eso ya es DFS y pierde la garantía del camino más corto. - Usar BFS con aristas de pesos distintos y creer que da el camino de menos kilómetros.
- En JavaScript,
shift()sobre un array es O(n): con colas enormes conviene un índice que avance en lugar de quitar el primero.
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. Grados de separación
Cada línea con dos nombres es una amistad (en los dos sentidos). Las líneas que empiezan por ? preguntan cómo llegar de una persona a otra con el mínimo de saltos: escribe el número de saltos y el camino. El main ya lee la entrada y guarda los amigos de cada persona en orden alfabético: completa camino.
- Amistad:
ana luis. Consulta:? ana pedro. - Respuesta:
ana → pedro: 2 saltos (ana, sara, pedro); con uno solo,1 salto; de alguien a sí mismo,0 saltos (ana). - Si no hay camino:
ana y eva no están conectados. Si un nombre no aparece en ninguna amistad:No conozco a zoe. - Si hay varios caminos igual de cortos, vale el que encuentra BFS recorriendo los amigos en orden alfabético (el código ya los guarda así). Cualquier otra línea:
Línea no válida: «…».
Ejemplo
ana luis luis marta marta pedro ana sara sara pedro ? ana pedro ? luis sara ? ana luis
ana → pedro: 2 saltos (ana, sara, pedro) luis → sara: 2 saltos (luis, ana, sara) ana → luis: 1 salto (ana, luis)
Ver la solución explicada
1import java.util.*;
2
3public class Main {
4 /** Los amigos de cada persona, en orden alfabético. */
5 static final Map<String, TreeSet<String>> amigos = new TreeMap<>();
6
7 /** El camino más corto (en saltos) de origen a destino, con los dos incluidos; null si no están conectados. */
8 static List<String> camino(String origen, String destino) {
9 Map<String, String> previo = new HashMap<>(); // de quién se descubre cada persona
10 Deque<String> cola = new ArrayDeque<>();
11 previo.put(origen, null);
12 cola.add(origen);
13 while (!cola.isEmpty()) {
14 String u = cola.poll();
15 if (u.equals(destino)) {
16 LinkedList<String> c = new LinkedList<>();
17 for (String n = destino; n != null; n = previo.get(n)) c.addFirst(n);
18 return c;
19 }
20 for (String v : amigos.get(u)) {
21 if (!previo.containsKey(v)) { // solo la primera vez: así es el más corto
22 previo.put(v, u);
23 cola.add(v);
24 }
25 }
26 }
27 return null;
28 }
29
30 public static void main(String[] args) {
31 Scanner sc = new Scanner(System.in);
32 while (sc.hasNextLine()) {
33 String linea = sc.nextLine().trim();
34 if (linea.isEmpty()) continue;
35 String[] p = linea.split("\\s+");
36 if (p[0].equals("?")) {
37 if (p.length != 3) {
38 System.out.println("Línea no válida: «" + linea + "»");
39 continue;
40 }
41 String a = p[1], b = p[2];
42 if (!amigos.containsKey(a)) System.out.println("No conozco a " + a);
43 else if (!amigos.containsKey(b)) System.out.println("No conozco a " + b);
44 else {
45 List<String> c = camino(a, b);
46 if (c == null) System.out.println(a + " y " + b + " no están conectados");
47 else System.out.println(a + " → " + b + ": " + (c.size() - 1) + (c.size() == 2 ? " salto (" : " saltos (") + String.join(", ", c) + ")");
48 }
49 } else if (p.length == 2 && !p[0].equals(p[1])) {
50 amigos.computeIfAbsent(p[0], k -> new TreeSet<>()).add(p[1]);
51 amigos.computeIfAbsent(p[1], k -> new TreeSet<>()).add(p[0]);
52 } else System.out.println("Línea no válida: «" + linea + "»");
53 }
54 }
55}Es un BFS de manual: la cola garantiza que se descubre a cada persona por el camino con menos saltos, y el mapa de previos sirve a la vez para no repetir y para reconstruir el camino.
Se puede parar en cuanto sale el destino de la cola (o incluso al descubrirlo): todo lo que quedaba por explorar está más lejos.
2. El caballo de ajedrez
En un tablero de n × n hay algunas casillas prohibidas. Para cada consulta, calcula el mínimo de saltos de caballo para ir de una casilla a otra sin pisar ninguna prohibida. No hay que construir el grafo: cada casilla es un nodo y sus vecinas son las (hasta 8) casillas a las que salta el caballo. La lectura y las casillas ya están escritas: completa saltos.
- Primera línea: n, entre 3 y 26 (si no,
Tamaño no válido: «…»y se termina). Segunda línea: las casillas prohibidas separadas por espacios, o-si no hay. - Después, una consulta por línea:
a1 h8. Las columnas son letras (a, b, c…) y las filas números desde 1. - Respuesta:
a1 → h8: 6 saltos(1 salto,0 saltos), oa1 → c3: imposible. - Errores:
Casilla no válida: «z9»,La casilla b3 está prohibida,Consulta no válida: «…».
Ejemplo
8 - a1 h8 a1 b3 d4 d4
a1 → h8: 6 saltos a1 → b3: 1 salto d4 → d4: 0 saltos
Ver la solución explicada
1import java.util.*;
2
3public class Main {
4 static int n; // el tablero es de n × n
5 static boolean[][] prohibida;
6 static final int[] DF = {1, 2, 2, 1, -1, -2, -2, -1}; // los 8 saltos del caballo
7 static final int[] DC = {2, 1, -1, -2, -2, -1, 1, 2};
8
9 /** "c5" → {fila, columna} contando desde 0; null si no es una casilla del tablero. */
10 static int[] casilla(String s) {
11 if (!s.matches("[a-z][1-9][0-9]?")) return null;
12 int c = s.charAt(0) - 'a', f = Integer.parseInt(s.substring(1)) - 1;
13 return f < n && c < n ? new int[]{f, c} : null;
14 }
15
16 /** El mínimo de saltos de caballo de (f0, c0) a (f1, c1) sin pisar casillas prohibidas; -1 si no se puede. */
17 static int saltos(int f0, int c0, int f1, int c1) {
18 int[][] dist = new int[n][n];
19 for (int[] fila : dist) Arrays.fill(fila, -1);
20 Deque<int[]> cola = new ArrayDeque<>();
21 dist[f0][c0] = 0;
22 cola.add(new int[]{f0, c0});
23 while (!cola.isEmpty()) {
24 int[] u = cola.poll();
25 if (u[0] == f1 && u[1] == c1) return dist[f1][c1];
26 for (int k = 0; k < 8; k++) {
27 int f = u[0] + DF[k], c = u[1] + DC[k];
28 if (f < 0 || f >= n || c < 0 || c >= n || prohibida[f][c] || dist[f][c] != -1) continue;
29 dist[f][c] = dist[u[0]][u[1]] + 1;
30 cola.add(new int[]{f, c});
31 }
32 }
33 return -1;
34 }
35
36 public static void main(String[] args) {
37 Scanner sc = new Scanner(System.in);
38 String primera = sc.hasNextLine() ? sc.nextLine().trim() : "";
39 if (!primera.matches("\\d{1,2}") || Integer.parseInt(primera) < 3 || Integer.parseInt(primera) > 26) {
40 System.out.println("Tamaño no válido: «" + primera + "»");
41 return;
42 }
43 n = Integer.parseInt(primera);
44 prohibida = new boolean[n][n];
45 String segunda = sc.hasNextLine() ? sc.nextLine().trim() : "-";
46 if (!segunda.equals("-")) {
47 for (String s : segunda.split("\\s+")) {
48 int[] p = casilla(s);
49 if (p == null) System.out.println("Casilla no válida: «" + s + "»");
50 else prohibida[p[0]][p[1]] = true;
51 }
52 }
53 while (sc.hasNextLine()) {
54 String linea = sc.nextLine().trim();
55 if (linea.isEmpty()) continue;
56 String[] q = linea.split("\\s+");
57 if (q.length != 2) {
58 System.out.println("Consulta no válida: «" + linea + "»");
59 continue;
60 }
61 int[] a = casilla(q[0]), b = casilla(q[1]);
62 if (a == null || b == null) System.out.println("Casilla no válida: «" + (a == null ? q[0] : q[1]) + "»");
63 else if (prohibida[a[0]][a[1]] || prohibida[b[0]][b[1]]) System.out.println("La casilla " + (prohibida[a[0]][a[1]] ? q[0] : q[1]) + " está prohibida");
64 else {
65 int s = saltos(a[0], a[1], b[0], b[1]);
66 System.out.println(q[0] + " → " + q[1] + ": " + (s < 0 ? "imposible" : s + (s == 1 ? " salto" : " saltos")));
67 }
68 }
69 }
70}Es un grafo implícito: los vecinos se calculan con los 8 desplazamientos del caballo en vez de leerlos de una lista. Muchísimos problemas de «mínimo número de movimientos» (puzles, tableros, laberintos) se resuelven así.
Cada consulta hace un BFS de como mucho n² casillas con 8 vecinas cada una: con n = 26 son menos de 6.000 operaciones.
Test
Test: Recorrido en anchura (BFS)
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.¿Qué estructura usa BFS para decidir qué nodo procesar después?
2.En un grafo sin pesos, ¿qué garantiza BFS?
3.¿Cuándo hay que marcar un nodo como visto en BFS?
4.¿Qué coste tiene BFS con una lista de adyacencia?
5.Si cada arista tiene una distancia en km distinta, ¿sirve BFS para el camino más corto?