Skip to Content

Caminos más cortos con pesos de arista no negativos

Casi todos los problemas de caminos más cortos de Oro involucran Dijkstra. Sin embargo, conviene aprender primero Bellman-Ford y Floyd-Warshall porque son más simples.

Bellman-Ford

Recursos
FuenteRecursoNotas
CPH13.1 - Bellman-Ford

hasta, pero sin incluir, “Negative Cycles”

Floyd-Warshall

Tutorial

Recursos
FuenteRecursoNotas
CPH13.3 - Floyd-Warshall

cálculo de ejemplo, código

cp-algoFloyd-Warshall

código, por qué funciona

PAPS12.3.3 - Floyd-Warshall

código, por qué funciona

CP24.5 - All-Pairs Shortest Paths
Floyd-Warshall incorrecto

De este artículo :

Un error habitual al implementar el algoritmo de Floyd–Warshall es desordenar los tres bucles anidados (el orden correcto es KIJ). Los algoritmos incorrectos IJK e IKJ no dan soluciones correctas para algunas instancias. Sin embargo, se puede demostrar que si se los repite tres veces, se obtienen las soluciones correctas.

Cabe enfatizar que estos arreglos (repetir algoritmos incorrectos tres veces) tienen la misma complejidad temporal que el algoritmo correcto de Floyd–Warshall salvo factores constantes. Por lo tanto, nuestros resultados sugieren que, si uno se confunde con el orden de los tres bucles anidados, se puede repetir el procedimiento tres veces por las dudas.

Problema

HechoFuenteNombreDificultadTagsSolución
CSESShortest Routes IIFácilAPSPen el módulo

Explicación

Este problema pide calcular caminos más cortos entre cualquier par de vértices. Por eso Floyd-Warshall es adecuado, por el NN bajo (N500N \le 500) y por la inclusión de pesos negativos.

Implementación

Complejidad temporal: O(N3)\mathcal{O}(N^3)

#include <iostream> #include <vector> using std::cout; using std::endl; using std::vector; constexpr long long BIG = 1e18; // No podemos usar LLONG_MAX por desbordamiento int main() { int n, m, q; std::cin >> n >> m >> q; vector<vector<long long>> min_dist(n, vector<long long>(n, BIG)); for (int i = 0; i < m; i++) { int a, b; int c; std::cin >> a >> b >> c; a--; b--; if (c < min_dist[a][b]) { min_dist[a][b] = min_dist[b][a] = c; } } // Algoritmo de Floyd-Warshall for (int k = 0; k < n; k++) { for (int i = 0; i < n; i++) { for (int j = i + 1; j < n; j++) { long long new_dist = min_dist[i][k] + min_dist[k][j]; if (new_dist < min_dist[i][j]) { min_dist[i][j] = min_dist[j][i] = new_dist; } } } } for (int i = 0; i < q; i++) { int a, b; std::cin >> a >> b; a--; b--; // Comprobar si los dos nodos son el mismo o no se pueden alcanzar if (a == b) { min_dist[a][b] = 0; } else if (min_dist[a][b] == BIG) { min_dist[a][b] = -1; } cout << min_dist[a][b] << '\n'; } }
import java.io.*; import java.util.*; public class ShortestRoutesII { // No podemos usar Long.MAX_VALUE por desbordamiento private static final long BIG = (long)1e18; public static void main(String[] args) { Kattio io = new Kattio(); int n = io.nextInt(); int m = io.nextInt(); int q = io.nextInt(); long[][] minDist = new long[n][n]; for (int i = 0; i < n; i++) { Arrays.fill(minDist[i], BIG); } for (int i = 0; i < m; i++) { int a = io.nextInt() - 1; int b = io.nextInt() - 1; int c = io.nextInt(); if (c < minDist[a][b]) { minDist[a][b] = minDist[b][a] = c; } } // Algoritmo de Floyd-Warshall for (int k = 0; k < n; k++) { for (int i = 0; i < n; i++) { for (int j = i + 1; j < n; j++) { long newDist = minDist[i][k] + minDist[k][j]; if (newDist < minDist[i][j]) { minDist[i][j] = minDist[j][i] = newDist; } } } } for (int i = 0; i < q; i++) { int a = io.nextInt() - 1; int b = io.nextInt() - 1; // Comprobar si los dos nodos son el mismo o no se pueden alcanzar if (a == b) { minDist[a][b] = 0; } else if (minDist[a][b] == BIG) { minDist[a][b] = -1; } io.println(minDist[a][b]); } io.close(); } // CodeSnip{Kattio} }
# Se usa 1e18 en lugar de float('inf') por rendimiento BIG = int(1e18) n, m, q = map(int, input().split()) min_dist = [[BIG] * n for _ in range(n)] for _ in range(m): a, b, c = map(int, input().split()) a, b = a - 1, b - 1 if c < min_dist[a][b]: min_dist[a][b] = min_dist[b][a] = c # Algoritmo de Floyd-Warshall for k in range(n): for i in range(n): for j in range(i + 1, n): new_dist = min_dist[i][k] + min_dist[k][j] if new_dist < min_dist[i][j]: min_dist[i][j] = min_dist[j][i] = new_dist for _ in range(q): a, b = map(int, input().split()) a, b = a - 1, b - 1 # Comprobar si los dos nodos son el mismo o no se pueden alcanzar if a == b: min_dist[a][b] = 0 elif min_dist[a][b] == BIG: min_dist[a][b] = -1 print(min_dist[a][b])

Problemas

Este algoritmo se usa como primer paso de lo siguiente:

HechoFuenteNombreDificultadTagsSolución
GoldMoortal CowmbatDifícilAPSP, DPSolución

Dijkstra

Tutorial

O(N2)\mathcal{O}(N^2)

Recursos
FuenteRecursoNotas
cp-algoDijkstra (Dense Graphs)

O(MlogN)\mathcal{O}(M\log N)

Recursos
FuenteRecursoNotas
CPH13.2 - Dijkstra

código

cp-algoDijkstra (Sparse Graphs)
CPC8 - Shortest Paths
CP24.4.3 - SSSP on Weighted Graph
alexyd88Dijkstra Visualizer

Implementación O(MlogN)\mathcal{O}(M\log N)

Recursos
FuenteRecursoNotas
BenqDijkstra

Problema

HechoFuenteNombreDificultadTagsSolución
CSESShortest Routes IFácilSPen el módulo

Implementación

Complejidad temporal: O(N+MlogN)\mathcal{O}(N + M\log N)

Aquí hay una animación de cómo funciona el algoritmo:

Recordemos del segundo módulo prerrequisito que podemos usar greater<> para hacer que el elemento del tope de una cola de prioridad sea el menor en lugar del mayor. Como alternativa, se pueden negar las distancias antes de ponerlas en la cola de prioridad.

#include <bits/stdc++.h> using namespace std; int main() { int n, m; cin >> n >> m; // Lista de adyacencia de (vecino, peso de arista) vector<vector<pair<int, int>>> neighbors(n); for (int i = 0; i < m; i++) { int a, b, c; cin >> a >> b >> c; neighbors[a - 1].push_back({b - 1, c}); } // Inicialmente fijamos todas las distancias en infinito vector<long long> dist(n, LLONG_MAX); // Algoritmo de Dijkstra using T = pair<long long, int>; priority_queue<T, vector<T>, greater<T>> pq; int start = 0; dist[start] = 0; // El camino más corto de un nodo a sí mismo es 0 pq.push({0, start}); while (!pq.empty()) { const auto [cdist, node] = pq.top(); pq.pop(); if (cdist != dist[node]) { continue; } for (const pair<int, int> &i : neighbors[node]) { // Si podemos alcanzar un nodo vecino más rápido, // actualizamos su distancia mínima if (cdist + i.second < dist[i.first]) { pq.push({dist[i.first] = cdist + i.second, i.first}); } } } for (int i = 0; i < n - 1; i++) { cout << dist[i] << ' '; } cout << dist[n - 1] << endl; }
import java.io.*; import java.util.*; public class ShortestRoutesI { // BeginCodeSnip{Pair Class} static class Pair<K, V> { public K a; public V b; public Pair(K a, V b) { this.a = a; this.b = b; } } // EndCodeSnip public static void main(String[] args) { Kattio io = new Kattio(); int n = io.nextInt(); int m = io.nextInt(); // Lista de adyacencia de (vecino, peso de arista) List<Pair<Integer, Integer>>[] neighbors = new ArrayList[n]; for (int i = 0; i < n; i++) { neighbors[i] = new ArrayList<>(); } for (int i = 0; i < m; i++) { int a = io.nextInt() - 1; int b = io.nextInt() - 1; int c = io.nextInt(); neighbors[a].add(new Pair<>(b, c)); } // Inicialmente fijamos todas las distancias en infinito long[] dist = new long[n]; Arrays.fill(dist, Long.MAX_VALUE); // Algoritmo de Dijkstra PriorityQueue<Pair<Long, Integer>> pq = new PriorityQueue<>(Comparator.comparingLong(i -> i.a)); int start = 0; dist[start] = 0; // El camino más corto de un nodo a sí mismo es 0 pq.add(new Pair<>(0L, start)); while (!pq.isEmpty()) { Pair<Long, Integer> curr = pq.poll(); long cdist = curr.a; int node = curr.b; if (cdist != dist[node]) { continue; } for (Pair<Integer, Integer> i : neighbors[node]) { // Si podemos alcanzar un nodo vecino más rápido, // actualizamos su distancia mínima if (cdist + i.b < dist[i.a]) { pq.add(new Pair<>(dist[i.a] = cdist + i.b, i.a)); } } } for (int i = 0; i < n - 1; i++) { io.print(dist[i] + " "); } io.println(dist[n - 1]); io.close(); } // CodeSnip{Kattio} }
import heapq n, m = map(int, input().split()) # Lista de adyacencia de (vecino, peso de arista) graph = [[] for _ in range(n)] for i in range(m): a, b, c = map(int, input().split()) graph[a - 1].append((b - 1, c)) # Inicialmente fijamos todas las distancias en infinito dist = [float("inf") for _ in range(n)] # Algoritmo de Dijkstra pq = [] start = 0 heapq.heappush(pq, (0, start)) dist[start] = 0 # El camino más corto de un nodo a sí mismo es 0 while pq: cdist, node = heapq.heappop(pq) if cdist != dist[node]: continue for i in graph[node]: # Si podemos alcanzar un nodo vecino más rápido, # actualizamos su distancia mínima if cdist + i[1] < dist[i[0]]: dist[i[0]] = cdist + i[1] heapq.heappush(pq, (dist[i[0]], i[0])) for i in range(n): print(dist[i], end=" ")
Dijkstra más rápido

Se puede hacer en O(M+NlogN)\mathcal{O}(M+N\log N) con heap de Fibonacci . En la práctica, sin embargo, rara vez es más rápido, porque el heap de Fibonacci tiene un mal factor constante.

Problemas

HechoFuenteNombreDificultadTagsSolución
CSESFlight DiscountFácilSPSolución
GoldMilk PumpingFácilSPSolución
GoldWhy Did the Cow Cross the RoadFácilSPSolución
GoldFine DiningFácilSPSolución
GoldShortcutNormalSPSolución
GoldTelephoneNormalSP
CFNot EscapingNormalSP, Coordinate Compression, Binary Search, DPSolución
CSESInvestigationNormalSPSolución
KattisRobot TurtlesNormalSPSolución
CSESFlight RoutesNormalSPSolución
ACRemainder GameDifícilSP, GreedySolución
IOI2011 - CrocodileDifícilSPSolución
JOI2018 - Commuter PassDifícilSP, DPSolución
JOI2021 - RobotDifícilSPSolución
APIOFind the PathDifícilSP, GeometrySolución
Balkan OI2012 - Shortest PathsDifícilSPSolución
Balkan OI2015 - CircusMuy difícilSP, StackSolución