Algoritmo de Dijkstra
El camino más corto desde un nodo a todos los demás en un grafo con pesos no negativos: fija cada vez el nodo pendiente más cercano con una cola de prioridad. Es lo que hay detrás de un GPS.
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.
Dijkstra
Escribe las aristas con su peso y el nodo de salida: Dijkstra encuentra la distancia más corta a todos los demás, fijando cada vez el más cercano.
- distancia provisional
Paso 1
Todas las distancias empiezan en ∞ menos la de A, que es 0. En la cola de prioridad entra (0, A).
1static Map<String, Integer> dijkstra(Map<String, Map<String, Integer>> g, String origen) {
2 Map<String, Integer> dist = new HashMap<>();
3 for (String v : g.keySet()) dist.put(v, Integer.MAX_VALUE);
4 dist.put(origen, 0); // dist = A=0 B=∞ C=∞ D=∞ E=∞ F=∞
5 PriorityQueue<Map.Entry<String, Integer>> cola = new PriorityQueue<>(Map.Entry.comparingByValue());
6 cola.add(Map.entry(origen, 0));
7 Set<String> fijos = new HashSet<>();
8 while (!cola.isEmpty()) {
9 String u = cola.poll().getKey();
10 if (!fijos.add(u)) continue;
11 for (var e : g.get(u).entrySet()) {
12 int nueva = dist.get(u) + e.getValue();
13 if (nueva < dist.get(e.getKey())) {
14 dist.put(e.getKey(), nueva);
15 cola.add(Map.entry(e.getKey(), nueva));
16 }
17 }
18 }
19 return dist;
20}Variables
- dist
- A=0 B=∞ C=∞ D=∞ E=∞ F=∞
Atajos con el foco dentro del visualizador: ← → paso a paso, Espacio reproducir o pausar, Inicio/Fin ir al principio o al final.
La idea
Cuando las aristas tienen pesos (kilómetros, minutos, euros), el camino con menos aristas ya no es el más corto: dos tramos de 100 km son mejores que uno de 300. El algoritmo de Dijkstra (1956) calcula las distancias mínimas desde un origen a todos los nodos, siempre que ningún peso sea negativo.
Mantiene una distancia provisional para cada nodo (∞ al principio, 0 el origen) y repite: coge el nodo pendiente con menor distancia provisional y lo fija, porque ya no puede mejorar. Después «relaja» sus aristas: para cada vecino, si llegar a través del nodo fijado es más corto que lo que se tenía, actualiza su distancia y apunta de dónde viene.
¿Por qué es seguro fijar el más cercano? Porque cualquier otro camino hasta él tendría que pasar por algún nodo pendiente, que ya está igual o más lejos, y después sumar pesos que no son negativos: no puede salir más corto. Por eso mismo falla con pesos negativos: una arista negativa encontrada tarde podría abaratar un nodo ya fijado.
Para coger rápido el más cercano se usa una cola de prioridad (un montículo). La versión habitual no actualiza las entradas que ya están en la cola: mete una nueva cada vez que una distancia mejora y, al sacar una entrada de un nodo ya fijado, la ignora. Es más sencilla y cuesta O((V + E) log V).
Cuándo usarlo
- Rutas en mapas: el GPS y los planificadores de viaje, con pesos en km o en minutos.
- Encaminamiento en redes: el protocolo OSPF calcula con Dijkstra las rutas de cada router.
- Cualquier problema de «coste mínimo» que se pueda ver como un grafo: el terreno más fácil en un mapa, la cadena de conversiones más barata.
- Juegos: el movimiento por terreno con costes distintos (A* es Dijkstra con una estimación de lo que falta).
Cuándo no
- Con pesos negativos: el resultado puede ser falso sin ningún aviso; usa Bellman-Ford.
- Si todas las aristas pesan lo mismo: BFS hace lo mismo más rápido y con menos código.
- Si necesitas las distancias entre todos los pares en un grafo pequeño y denso: Floyd-Warshall es más directo.
- Si solo te interesa un destino y tienes una buena estimación de lo que falta (la distancia en línea recta): A* explora mucho menos.
Paso a paso
- Inicializar. Distancia 0 al origen e ∞ al resto; mete (0, origen) en la cola de prioridad.
- Sacar el más cercano. Saca la entrada de menor distancia. Si su nodo ya estaba fijado, es una entrada vieja: ignórala. Si no, fíjalo.
- Relajar sus aristas. Para cada vecino, suma la distancia del nodo fijado y el peso de la arista. Si mejora la que tenía, actualízala, apunta el anterior y mete la nueva entrada en la cola.
- Reconstruir. Cuando la cola se vacía, las distancias son definitivas. Para el camino, sigue los anteriores desde el destino hasta el origen.
El código
Rutas entre ciudades
Distancias aproximadas por carretera. Se imprime el orden en que se fijan las ciudades (de más cerca a más lejos) y dos rutas reconstruidas con los anteriores.
1import java.util.*;
2
3public class Main {
4 static final Map<String, Map<String, Integer>> mapa = new LinkedHashMap<>();
5
6 static void carretera(String a, String b, int km) {
7 mapa.computeIfAbsent(a, k -> new LinkedHashMap<>()).put(b, km);
8 mapa.computeIfAbsent(b, k -> new LinkedHashMap<>()).put(a, km);
9 }
10
11 record Entrada(String ciudad, int km) { }
12
13 public static void main(String[] args) {
14 carretera("Madrid", "Zaragoza", 315); // km aproximados
15 carretera("Zaragoza", "Barcelona", 300);
16 carretera("Madrid", "Valencia", 355);
17 carretera("Valencia", "Barcelona", 350);
18 carretera("Madrid", "Bilbao", 400);
19 carretera("Bilbao", "Zaragoza", 305);
20 carretera("Madrid", "Granada", 420);
21 carretera("Granada", "Málaga", 125);
22 carretera("Madrid", "Sevilla", 530);
23 carretera("Sevilla", "Málaga", 205);
24 carretera("Sevilla", "Granada", 250);
25 carretera("Valencia", "Murcia", 240);
26 carretera("Murcia", "Granada", 280);
27
28 Map<String, Integer> dist = new HashMap<>();
29 Map<String, String> previa = new HashMap<>();
30 Set<String> fijas = new LinkedHashSet<>(); // en el orden en que se fijan
31 PriorityQueue<Entrada> cola = new PriorityQueue<>(Comparator.comparingInt(Entrada::km));
32 dist.put("Madrid", 0);
33 cola.add(new Entrada("Madrid", 0));
34 while (!cola.isEmpty()) {
35 Entrada e = cola.poll(); // la más cercana de las pendientes
36 if (!fijas.add(e.ciudad())) continue; // entrada vieja: ya estaba fijada
37 for (Map.Entry<String, Integer> c : mapa.get(e.ciudad()).entrySet()) {
38 int nueva = e.km() + c.getValue();
39 if (nueva < dist.getOrDefault(c.getKey(), Integer.MAX_VALUE)) {
40 dist.put(c.getKey(), nueva);
41 previa.put(c.getKey(), e.ciudad());
42 cola.add(new Entrada(c.getKey(), nueva));
43 }
44 }
45 }
46 System.out.println("Orden en que se fijan las ciudades:");
47 for (String c : fijas) System.out.println(" " + c + ": " + dist.get(c) + " km");
48 for (String destino : List.of("Barcelona", "Málaga")) {
49 LinkedList<String> camino = new LinkedList<>();
50 for (String c = destino; c != null; c = previa.get(c)) camino.addFirst(c);
51 System.out.println("Madrid → " + destino + ": " + String.join(" → ", camino));
52 }
53 }
54}import heapq
mapa = {}
def carretera(a, b, km):
mapa.setdefault(a, {})[b] = km
mapa.setdefault(b, {})[a] = km
carretera("Madrid", "Zaragoza", 315) # km aproximados
carretera("Zaragoza", "Barcelona", 300)
carretera("Madrid", "Valencia", 355)
carretera("Valencia", "Barcelona", 350)
carretera("Madrid", "Bilbao", 400)
carretera("Bilbao", "Zaragoza", 305)
carretera("Madrid", "Granada", 420)
carretera("Granada", "Málaga", 125)
carretera("Madrid", "Sevilla", 530)
carretera("Sevilla", "Málaga", 205)
carretera("Sevilla", "Granada", 250)
carretera("Valencia", "Murcia", 240)
carretera("Murcia", "Granada", 280)
dist = {"Madrid": 0}
previa = {}
fijas = {} # un dict conserva el orden: el de fijarse
cola = [(0, "Madrid")]
while cola:
km, ciudad = heapq.heappop(cola) # la más cercana de las pendientes
if ciudad in fijas:
continue # entrada vieja: ya estaba fijada
fijas[ciudad] = km
for vecina, tramo in mapa[ciudad].items():
nueva = km + tramo
if nueva < dist.get(vecina, float("inf")):
dist[vecina] = nueva
previa[vecina] = ciudad
heapq.heappush(cola, (nueva, vecina))
print("Orden en que se fijan las ciudades:")
for c, km in fijas.items():
print(f" {c}: {km} km")
for destino in ["Barcelona", "Málaga"]:
camino = []
c = destino
while c is not None:
camino.insert(0, c)
c = previa.get(c)
print(f"Madrid → {destino}: " + " → ".join(camino))const mapa = new Map();
function carretera(a, b, km) {
if (!mapa.has(a)) mapa.set(a, new Map());
if (!mapa.has(b)) mapa.set(b, new Map());
mapa.get(a).set(b, km);
mapa.get(b).set(a, km);
}
carretera("Madrid", "Zaragoza", 315); // km aproximados
carretera("Zaragoza", "Barcelona", 300);
carretera("Madrid", "Valencia", 355);
carretera("Valencia", "Barcelona", 350);
carretera("Madrid", "Bilbao", 400);
carretera("Bilbao", "Zaragoza", 305);
carretera("Madrid", "Granada", 420);
carretera("Granada", "Málaga", 125);
carretera("Madrid", "Sevilla", 530);
carretera("Sevilla", "Málaga", 205);
carretera("Sevilla", "Granada", 250);
carretera("Valencia", "Murcia", 240);
carretera("Murcia", "Granada", 280);
const dist = new Map([["Madrid", 0]]);
const previa = new Map();
const fijas = new Set(); // un Set conserva el orden: el de fijarse
const cola = [[0, "Madrid"]]; // sin cola de prioridad en la biblioteca: un array que se ordena
while (cola.length > 0) {
cola.sort((x, y) => x[0] - y[0]);
const [km, ciudad] = cola.shift(); // la más cercana de las pendientes
if (fijas.has(ciudad)) continue; // entrada vieja: ya estaba fijada
fijas.add(ciudad);
for (const [vecina, tramo] of mapa.get(ciudad)) {
const nueva = km + tramo;
if (nueva < (dist.get(vecina) ?? Infinity)) {
dist.set(vecina, nueva);
previa.set(vecina, ciudad);
cola.push([nueva, vecina]);
}
}
}
console.log("Orden en que se fijan las ciudades:");
for (const c of fijas) console.log(` ${c}: ${dist.get(c)} km`);
for (const destino of ["Barcelona", "Málaga"]) {
const camino = [];
for (let c = destino; c !== undefined; c = previa.get(c)) camino.unshift(c);
console.log(`Madrid → ${destino}: ${camino.join(" → ")}`);
}using System;
using System.Collections.Generic;
class Program {
static readonly Dictionary<string, Dictionary<string, int>> mapa = new();
static void Carretera(string a, string b, int km) {
if (!mapa.ContainsKey(a)) mapa[a] = new Dictionary<string, int>();
if (!mapa.ContainsKey(b)) mapa[b] = new Dictionary<string, int>();
mapa[a][b] = km;
mapa[b][a] = km;
}
static void Main() {
Carretera("Madrid", "Zaragoza", 315); // km aproximados
Carretera("Zaragoza", "Barcelona", 300);
Carretera("Madrid", "Valencia", 355);
Carretera("Valencia", "Barcelona", 350);
Carretera("Madrid", "Bilbao", 400);
Carretera("Bilbao", "Zaragoza", 305);
Carretera("Madrid", "Granada", 420);
Carretera("Granada", "Málaga", 125);
Carretera("Madrid", "Sevilla", 530);
Carretera("Sevilla", "Málaga", 205);
Carretera("Sevilla", "Granada", 250);
Carretera("Valencia", "Murcia", 240);
Carretera("Murcia", "Granada", 280);
var dist = new Dictionary<string, int> { ["Madrid"] = 0 };
var previa = new Dictionary<string, string>();
var fijas = new List<string>(); // en el orden en que se fijan
var cola = new PriorityQueue<string, int>();
cola.Enqueue("Madrid", 0);
while (cola.TryDequeue(out var ciudad, out int km)) { // la más cercana de las pendientes
if (fijas.Contains(ciudad)) continue; // entrada vieja: ya estaba fijada
fijas.Add(ciudad);
foreach (var (vecina, tramo) in mapa[ciudad]) {
int nueva = km + tramo;
if (nueva < dist.GetValueOrDefault(vecina, int.MaxValue)) {
dist[vecina] = nueva;
previa[vecina] = ciudad;
cola.Enqueue(vecina, nueva);
}
}
}
Console.WriteLine("Orden en que se fijan las ciudades:");
foreach (string c in fijas) Console.WriteLine(quot; {c}: {dist[c]} km");
foreach (string destino in new[] { "Barcelona", "Málaga" }) {
var camino = new List<string>();
for (string? c = destino; c != null; c = previa.GetValueOrDefault(c)) camino.Insert(0, c);
Console.WriteLine(quot;Madrid → {destino}: {string.Join(" → ", camino)}");
}
}
}<?php
$mapa = [];
function carretera(array &$mapa, string $a, string $b, int $km): void {
$mapa[$a][$b] = $km;
$mapa[$b][$a] = $km;
}
carretera($mapa, "Madrid", "Zaragoza", 315); // km aproximados
carretera($mapa, "Zaragoza", "Barcelona", 300);
carretera($mapa, "Madrid", "Valencia", 355);
carretera($mapa, "Valencia", "Barcelona", 350);
carretera($mapa, "Madrid", "Bilbao", 400);
carretera($mapa, "Bilbao", "Zaragoza", 305);
carretera($mapa, "Madrid", "Granada", 420);
carretera($mapa, "Granada", "Málaga", 125);
carretera($mapa, "Madrid", "Sevilla", 530);
carretera($mapa, "Sevilla", "Málaga", 205);
carretera($mapa, "Sevilla", "Granada", 250);
carretera($mapa, "Valencia", "Murcia", 240);
carretera($mapa, "Murcia", "Granada", 280);
$dist = ["Madrid" => 0];
$previa = [];
$fijas = []; // las claves conservan el orden: el de fijarse
$cola = new SplPriorityQueue(); // saca la MAYOR prioridad: se usa -km
$cola->insert(["Madrid", 0], 0);
while (!$cola->isEmpty()) {
[$ciudad, $km] = $cola->extract(); // la más cercana de las pendientes
if (isset($fijas[$ciudad])) continue; // entrada vieja: ya estaba fijada
$fijas[$ciudad] = $km;
foreach ($mapa[$ciudad] as $vecina => $tramo) {
$nueva = $km + $tramo;
if ($nueva < ($dist[$vecina] ?? PHP_INT_MAX)) {
$dist[$vecina] = $nueva;
$previa[$vecina] = $ciudad;
$cola->insert([$vecina, $nueva], -$nueva);
}
}
}
echo "Orden en que se fijan las ciudades:\n";
foreach ($fijas as $c => $km) echo " $c: $km km\n";
foreach (["Barcelona", "Málaga"] as $destino) {
$camino = [];
for ($c = $destino; $c !== null; $c = $previa[$c] ?? null) array_unshift($camino, $c);
echo "Madrid → $destino: " . implode(" → ", $camino) . "\n";
}Salida al ejecutarlo (la misma en los 5 lenguajes)
Orden en que se fijan las ciudades: Madrid: 0 km Zaragoza: 315 km Valencia: 355 km Bilbao: 400 km Granada: 420 km Sevilla: 530 km Málaga: 545 km Murcia: 595 km Barcelona: 615 km Madrid → Barcelona: Madrid → Zaragoza → Barcelona Madrid → Málaga: Madrid → Granada → Málaga
Con pesos negativos, Dijkstra se equivoca
Un grafo dirigido de cuatro nodos con una arista negativa. Bellman-Ford, que repite la relajación de todas las aristas V − 1 veces, da la respuesta correcta.
1import java.util.*;
2
3public class Main {
4 record Arista(String de, String a, int peso) { }
5 record Entrada(String nodo, int d) { }
6
7 static final List<String> NODOS = List.of("A", "B", "C", "D");
8 static final List<Arista> ARISTAS = List.of(new Arista("A", "B", 2), new Arista("A", "C", 5),
9 new Arista("C", "B", -4), new Arista("B", "D", 1));
10
11 static Map<String, Integer> dijkstra(String origen) {
12 Map<String, Integer> dist = new LinkedHashMap<>();
13 for (String n : NODOS) dist.put(n, Integer.MAX_VALUE);
14 dist.put(origen, 0);
15 Set<String> fijos = new HashSet<>();
16 PriorityQueue<Entrada> cola = new PriorityQueue<>(Comparator.comparingInt(Entrada::d));
17 cola.add(new Entrada(origen, 0));
18 while (!cola.isEmpty()) {
19 Entrada e = cola.poll();
20 if (!fijos.add(e.nodo())) continue;
21 for (Arista a : ARISTAS)
22 if (a.de().equals(e.nodo()) && e.d() + a.peso() < dist.get(a.a())) {
23 dist.put(a.a(), e.d() + a.peso());
24 cola.add(new Entrada(a.a(), e.d() + a.peso()));
25 }
26 }
27 return dist;
28 }
29
30 /** Bellman-Ford: relaja TODAS las aristas V − 1 veces. Más lento, pero admite pesos negativos. */
31 static Map<String, Integer> bellmanFord(String origen) {
32 Map<String, Integer> dist = new LinkedHashMap<>();
33 for (String n : NODOS) dist.put(n, Integer.MAX_VALUE);
34 dist.put(origen, 0);
35 for (int i = 1; i < NODOS.size(); i++)
36 for (Arista a : ARISTAS)
37 if (dist.get(a.de()) != Integer.MAX_VALUE && dist.get(a.de()) + a.peso() < dist.get(a.a()))
38 dist.put(a.a(), dist.get(a.de()) + a.peso());
39 return dist;
40 }
41
42 static String texto(Map<String, Integer> dist) {
43 StringJoiner sj = new StringJoiner(" ");
44 dist.forEach((n, d) -> sj.add(n + "=" + d));
45 return sj.toString();
46 }
47
48 public static void main(String[] args) {
49 System.out.println("Dijkstra: " + texto(dijkstra("A")));
50 System.out.println("Bellman-Ford: " + texto(bellmanFord("A")));
51 System.out.println("D sale mal con Dijkstra: B se fijó con 2 y, cuando C → B lo bajó a 1, ya no se propagó a D.");
52 }
53}import heapq
NODOS = ["A", "B", "C", "D"]
ARISTAS = [("A", "B", 2), ("A", "C", 5), ("C", "B", -4), ("B", "D", 1)]
INF = float("inf")
def dijkstra(origen):
dist = {n: INF for n in NODOS}
dist[origen] = 0
fijos = set()
cola = [(0, origen)]
while cola:
d, u = heapq.heappop(cola)
if u in fijos:
continue
fijos.add(u)
for de, a, peso in ARISTAS:
if de == u and d + peso < dist[a]:
dist[a] = d + peso
heapq.heappush(cola, (d + peso, a))
return dist
def bellman_ford(origen):
"""Relaja TODAS las aristas V − 1 veces. Más lento, pero admite pesos negativos."""
dist = {n: INF for n in NODOS}
dist[origen] = 0
for _ in range(len(NODOS) - 1):
for de, a, peso in ARISTAS:
if dist[de] != INF and dist[de] + peso < dist[a]:
dist[a] = dist[de] + peso
return dist
def texto(dist):
return " ".join(f"{n}={d}" for n, d in dist.items())
print("Dijkstra: " + texto(dijkstra("A")))
print("Bellman-Ford: " + texto(bellman_ford("A")))
print("D sale mal con Dijkstra: B se fijó con 2 y, cuando C → B lo bajó a 1, ya no se propagó a D.")const NODOS = ["A", "B", "C", "D"];
const ARISTAS = [["A", "B", 2], ["A", "C", 5], ["C", "B", -4], ["B", "D", 1]];
function dijkstra(origen) {
const dist = new Map(NODOS.map((n) => [n, Infinity]));
dist.set(origen, 0);
const fijos = new Set();
const cola = [[0, origen]]; // un array que se ordena hace de cola de prioridad
while (cola.length > 0) {
cola.sort((x, y) => x[0] - y[0]);
const [d, u] = cola.shift();
if (fijos.has(u)) continue;
fijos.add(u);
for (const [de, a, peso] of ARISTAS)
if (de === u && d + peso < dist.get(a)) {
dist.set(a, d + peso);
cola.push([d + peso, a]);
}
}
return dist;
}
/** Bellman-Ford: relaja TODAS las aristas V − 1 veces. Más lento, pero admite pesos negativos. */
function bellmanFord(origen) {
const dist = new Map(NODOS.map((n) => [n, Infinity]));
dist.set(origen, 0);
for (let i = 1; i < NODOS.length; i++)
for (const [de, a, peso] of ARISTAS)
if (dist.get(de) !== Infinity && dist.get(de) + peso < dist.get(a)) dist.set(a, dist.get(de) + peso);
return dist;
}
const texto = (dist) => [...dist].map(([n, d]) => `${n}=${d}`).join(" ");
console.log("Dijkstra: " + texto(dijkstra("A")));
console.log("Bellman-Ford: " + texto(bellmanFord("A")));
console.log("D sale mal con Dijkstra: B se fijó con 2 y, cuando C → B lo bajó a 1, ya no se propagó a D.");using System;
using System.Collections.Generic;
using System.Linq;
class Program {
static readonly string[] Nodos = { "A", "B", "C", "D" };
static readonly (string De, string A, int Peso)[] Aristas = { ("A", "B", 2), ("A", "C", 5), ("C", "B", -4), ("B", "D", 1) };
static Dictionary<string, int> Dijkstra(string origen) {
var dist = Nodos.ToDictionary(n => n, _ => int.MaxValue);
dist[origen] = 0;
var fijos = new HashSet<string>();
var cola = new PriorityQueue<string, int>();
cola.Enqueue(origen, 0);
while (cola.TryDequeue(out var u, out int d)) {
if (!fijos.Add(u)) continue;
foreach (var (de, a, peso) in Aristas)
if (de == u && d + peso < dist[a]) {
dist[a] = d + peso;
cola.Enqueue(a, d + peso);
}
}
return dist;
}
// Bellman-Ford: relaja TODAS las aristas V − 1 veces. Más lento, pero admite pesos negativos.
static Dictionary<string, int> BellmanFord(string origen) {
var dist = Nodos.ToDictionary(n => n, _ => int.MaxValue);
dist[origen] = 0;
for (int i = 1; i < Nodos.Length; i++)
foreach (var (de, a, peso) in Aristas)
if (dist[de] != int.MaxValue && dist[de] + peso < dist[a]) dist[a] = dist[de] + peso;
return dist;
}
static string Texto(Dictionary<string, int> dist) => string.Join(" ", dist.Select(p => quot;{p.Key}={p.Value}"));
static void Main() {
Console.WriteLine("Dijkstra: " + Texto(Dijkstra("A")));
Console.WriteLine("Bellman-Ford: " + Texto(BellmanFord("A")));
Console.WriteLine("D sale mal con Dijkstra: B se fijó con 2 y, cuando C → B lo bajó a 1, ya no se propagó a D.");
}
}<?php
const NODOS = ["A", "B", "C", "D"];
const ARISTAS = [["A", "B", 2], ["A", "C", 5], ["C", "B", -4], ["B", "D", 1]];
function dijkstra(string $origen): array {
$dist = array_fill_keys(NODOS, PHP_INT_MAX);
$dist[$origen] = 0;
$fijos = [];
$cola = new SplPriorityQueue(); // saca la MAYOR prioridad: se usa -distancia
$cola->insert([$origen, 0], 0);
while (!$cola->isEmpty()) {
[$u, $d] = $cola->extract();
if (isset($fijos[$u])) continue;
$fijos[$u] = true;
foreach (ARISTAS as [$de, $a, $peso])
if ($de === $u && $d + $peso < $dist[$a]) {
$dist[$a] = $d + $peso;
$cola->insert([$a, $d + $peso], -($d + $peso));
}
}
return $dist;
}
/** Bellman-Ford: relaja TODAS las aristas V − 1 veces. Más lento, pero admite pesos negativos. */
function bellmanFord(string $origen): array {
$dist = array_fill_keys(NODOS, PHP_INT_MAX);
$dist[$origen] = 0;
for ($i = 1; $i < count(NODOS); $i++)
foreach (ARISTAS as [$de, $a, $peso])
if ($dist[$de] !== PHP_INT_MAX && $dist[$de] + $peso < $dist[$a]) $dist[$a] = $dist[$de] + $peso;
return $dist;
}
function texto(array $dist): string {
return implode(" ", array_map(fn($n, $d) => "$n=$d", array_keys($dist), $dist));
}
echo "Dijkstra: " . texto(dijkstra("A")) . "\n";
echo "Bellman-Ford: " . texto(bellmanFord("A")) . "\n";
echo "D sale mal con Dijkstra: B se fijó con 2 y, cuando C → B lo bajó a 1, ya no se propagó a D.\n";Salida al ejecutarlo (la misma en los 5 lenguajes)
Dijkstra: A=0 B=1 C=5 D=3 Bellman-Ford: A=0 B=1 C=5 D=2 D sale mal con Dijkstra: B se fijó con 2 y, cuando C → B lo bajó a 1, ya no se propagó a D.
Traza: Dijkstra desde A en el grafo del visualizador
| Sale de la cola | Qué pasa | Mejoras | Distancias |
|---|---|---|---|
| A:0 | se fija A | B → 4, C → 2 | A=0 B=4 C=2 D=∞ E=∞ F=∞ |
| C:2 | se fija C | B → 3, D → 10, E → 12 | A=0 B=3 C=2 D=10 E=12 F=∞ |
| B:3 | se fija B | D → 8 | A=0 B=3 C=2 D=8 E=12 F=∞ |
| B:4 | entrada vieja: se ignora | — | A=0 B=3 C=2 D=8 E=12 F=∞ |
| D:8 | se fija D | E → 10, F → 14 | A=0 B=3 C=2 D=8 E=10 F=14 |
| D:10 | entrada vieja: se ignora | — | A=0 B=3 C=2 D=8 E=10 F=14 |
| E:10 | se fija E | F → 12 | A=0 B=3 C=2 D=8 E=10 F=12 |
| E:12 | entrada vieja: se ignora | — | A=0 B=3 C=2 D=8 E=10 F=12 |
| F:12 | se fija F | — | A=0 B=3 C=2 D=8 E=10 F=12 |
| F:14 | entrada vieja: se ignora | — | A=0 B=3 C=2 D=8 E=10 F=12 |
Las entradas viejas (B:4, D:10, E:12, F:14) se quedan en la cola y se descartan al salir: es más barato que buscarlas para actualizarlas.
Complejidad
| Versión | Tiempo | Cuándo conviene |
|---|---|---|
| Dijkstra con montículo binario | O((V + E) log V) | Grafos dispersos: lo normal (mapas, redes) |
| Dijkstra con array (buscar el mínimo a mano) | O(V²) | Grafos muy densos (E cerca de V²) |
| BFS (todas las aristas pesan 1) | O(V + E) | Sin pesos |
| Bellman-Ford | O(V · E) | Hay pesos negativos (y detecta los ciclos negativos) |
| Floyd-Warshall | O(V³) | Todas las parejas en un grafo pequeño |
Cada mejora mete una entrada en la cola (como mucho E) y cada salida cuesta log: de ahí el (V + E) log V.
- Mejor caso: O(n log n)
- Caso medio: O(n log n)
- Peor caso: O(n log n)
O((V + E) log V) con un montículo: cada mejora mete una entrada en la cola y cada salida cuesta log. Sin montículo (buscando el mínimo a mano) es O(V²). Las curvas grises son las demás clases, para comparar.
En la práctica
- Google Maps y los GPS usan variantes de Dijkstra y A* con mucho preprocesado del mapa (jerarquías de carreteras) para responder en milisegundos.
- OSPF e IS-IS, los protocolos de encaminamiento de las redes grandes, calculan con Dijkstra el árbol de caminos más cortos de cada router.
- Los videojuegos calculan rutas por terrenos con costes distintos (A* es Dijkstra con una heurística).
- Las aplicaciones de transporte público combinan tiempos de trayecto y de transbordo como pesos.
- Java no trae un Dijkstra en la biblioteca estándar, pero sí la pieza clave:
PriorityQueue.
Errores típicos
- Usarlo con pesos negativos: da distancias falsas sin avisar.
- No descartar las entradas viejas al sacarlas: se vuelve a procesar un nodo con una distancia peor.
- Fijar un nodo al meterlo en la cola en vez de al sacarlo.
- Inicializar con
Integer.MAX_VALUEy sumarle un peso: desborda y se vuelve negativo. Comprueba antes que la distancia no es «infinita». - Comparar en la cola por el nombre del nodo, o meter el nodo sin su distancia: la cola de prioridad tiene que ordenar por distancia.
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. La ruta más corta entre ciudades
Cada línea con dos ciudades y un número es una carretera de doble sentido con sus kilómetros. Las líneas que empiezan por ? preguntan la ruta más corta entre dos ciudades. El main ya guarda las carreteras y reconstruye la ruta con previa: completa dijkstra.
- Carretera:
Madrid Zaragoza 315(si una carretera aparece dos veces, vale la más corta). Consulta:? Madrid Barcelona. - Respuesta:
Madrid → Barcelona: 615 km (Madrid, Zaragoza, Barcelona); de una ciudad a sí misma,0 km (Madrid). - Si no hay ruta:
Madrid → Palma: sin ruta. Ciudad que no está en ninguna carretera:No conozco Lisboa. Otra cosa:Línea no válida: «…». - En las pruebas, la ruta más corta siempre es única.
Ejemplo
Madrid Zaragoza 315 Zaragoza Barcelona 300 Madrid Valencia 355 Valencia Barcelona 350 Madrid Bilbao 400 Bilbao Zaragoza 305 Madrid Granada 420 Granada Malaga 125 Madrid Sevilla 530 Sevilla Malaga 205 Sevilla Granada 250 Valencia Murcia 240 Murcia Granada 280 ? Madrid Barcelona ? Madrid Malaga ? Barcelona Sevilla
Madrid → Barcelona: 615 km (Madrid, Zaragoza, Barcelona) Madrid → Malaga: 545 km (Madrid, Granada, Malaga) Barcelona → Sevilla: 1120 km (Barcelona, Valencia, Murcia, Granada, Sevilla)
Ver la solución explicada
1import java.util.*;
2
3public class Main {
4 static final Map<String, Map<String, Integer>> carreteras = new TreeMap<>();
5 static final Map<String, Integer> dist = new HashMap<>(); // km desde el origen
6 static final Map<String, String> previa = new HashMap<>(); // desde qué ciudad se llega a cada una
7
8 record Entrada(String ciudad, int km) { }
9
10 /** Dijkstra desde origen: rellena dist (solo con las ciudades alcanzables) y previa. */
11 static void dijkstra(String origen) {
12 PriorityQueue<Entrada> cola = new PriorityQueue<>(Comparator.comparingInt(Entrada::km));
13 Set<String> fijas = new HashSet<>();
14 dist.put(origen, 0);
15 cola.add(new Entrada(origen, 0));
16 while (!cola.isEmpty()) {
17 Entrada e = cola.poll();
18 if (!fijas.add(e.ciudad())) continue; // entrada vieja
19 for (Map.Entry<String, Integer> c : carreteras.get(e.ciudad()).entrySet()) {
20 int nueva = e.km() + c.getValue();
21 if (nueva < dist.getOrDefault(c.getKey(), Integer.MAX_VALUE)) {
22 dist.put(c.getKey(), nueva);
23 previa.put(c.getKey(), e.ciudad());
24 cola.add(new Entrada(c.getKey(), nueva));
25 }
26 }
27 }
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("?") && p.length == 3) {
37 String a = p[1], b = p[2];
38 if (!carreteras.containsKey(a) || !carreteras.containsKey(b)) {
39 System.out.println("No conozco " + (carreteras.containsKey(a) ? b : a));
40 continue;
41 }
42 dist.clear();
43 previa.clear();
44 dijkstra(a);
45 if (!dist.containsKey(b)) {
46 System.out.println(a + " → " + b + ": sin ruta");
47 continue;
48 }
49 LinkedList<String> ruta = new LinkedList<>();
50 for (String c = b; !c.equals(a); c = previa.get(c)) ruta.addFirst(c);
51 ruta.addFirst(a);
52 System.out.println(a + " → " + b + ": " + dist.get(b) + " km (" + String.join(", ", ruta) + ")");
53 } else if (p.length == 3 && !p[0].equals("?") && !p[0].equals(p[1]) && p[2].matches("\\d{1,5}")) {
54 int km = Integer.parseInt(p[2]);
55 carreteras.computeIfAbsent(p[0], k -> new TreeMap<>()).merge(p[1], km, Math::min);
56 carreteras.computeIfAbsent(p[1], k -> new TreeMap<>()).merge(p[0], km, Math::min);
57 } else System.out.println("Línea no válida: «" + linea + "»");
58 }
59 }
60}Es el Dijkstra de la ficha sobre un grafo leído de la entrada. previa guarda el último paso de la ruta más corta a cada ciudad; seguirlo hacia atrás desde el destino da la ruta entera.
Ojo con la primera prueba: de Barcelona a Sevilla la ruta más corta no pasa por Madrid, sino por Valencia, Murcia y Granada. Contar tramos (BFS) daría otra.
2. El terreno más fácil
El mapa es una cuadrícula de dígitos del 1 al 9: lo que cuesta entrar en esa casilla. Las casillas # son muros. Calcula el coste mínimo para ir de la esquina de arriba a la izquierda a la de abajo a la derecha moviéndote en horizontal o vertical (la casilla de salida no se paga). La lectura del mapa ya está hecha: completa costeMinimo.
- Entrada: el mapa, una fila por línea, todas del mismo ancho.
- Salida:
Coste mínimo: 40, oSin caminosi no se puede llegar (o si la salida o la llegada son muros). - Si una fila tiene otro ancho u otros caracteres:
Mapa no válido en la fila 3: «…».
Ejemplo
11637 13813 21365 36949 74634
Coste mínimo: 24