29. Algoritmo de Floyd-Warshall

Floyd-Warshall calcula las distancias mínimas entre todos los pares de vértices. Utiliza programación dinámica y una matriz que mejora al incorporar cada nodo como posible intermediario.

29.1 Introducción

Dijkstra y Bellman-Ford resuelven caminos mínimos desde un origen. Cuando necesitamos responder consultas entre muchos pares, repetir un algoritmo puede ser posible, pero existe una alternativa matricial especialmente clara: Floyd-Warshall.

El algoritmo calcula simultáneamente la distancia mínima desde cada vértice hacia todos los demás. Admite pesos negativos, siempre que interpretemos correctamente los efectos de los ciclos negativos.

29.2 Problema que resuelve

Dado un grafo dirigido o no dirigido ponderado, Floyd-Warshall produce una matriz D tal que:

D[i][j] = costo mínimo de un camino desde i hasta j

Si j no es alcanzable desde i, el valor permanece en infinito. El resultado contiene V² respuestas, una para cada par ordenado.

29.3 Matriz inicial

Antes de comenzar se construye la matriz:

D[i][i] = 0
D[i][j] = peso(i,j), si existe la arista i → j
D[i][j] = ∞, si no existe conexión directa

Si existen varias aristas entre el mismo par, se conserva inicialmente la de menor peso.

29.4 Idea de programación dinámica

Los vértices se consideran progresivamente como posibles intermediarios. Para cada k se pregunta si el camino i → k → j mejora el mejor camino conocido de i hasta j.

D[i][j] = min(D[i][j], D[i][k] + D[k][j])

La fórmula resume dos posibilidades: conservar el camino anterior o utilizar k para unir un camino mínimo de i a k con otro de k a j.

29.5 Significado de cada etapa

Después de procesar el vértice k, D[i][j] contiene el mejor costo usando únicamente los vértices ya considerados como intermediarios.

Invariante: tras la etapa k, los intermediarios permitidos son 0, 1, ..., k.

Al finalizar la última etapa, todos los vértices pueden actuar como intermediarios y la matriz contiene las distancias mínimas globales.

29.6 Simulación interactiva paso a paso

Avanza un intermediario por vez. Las celdas rosas mejoraron en la última etapa. Puedes agregar C → A con peso 1 para crear el ciclo negativo A → E → D → C → A.

Matriz de distancias

Acción actual

Matriz inicial.
Grafo sin ciclo negativo.
Etapa0 / 5
Intermediario
Mejoras de la etapa0
Ciclo negativoNo detectado

Una celda roja en la diagonal indica una distancia D[i][i] negativa y, por lo tanto, un ciclo negativo alcanzable desde i y capaz de regresar a i.

29.7 Implementación básica

function floydWarshall(matriz) {
  const n = matriz.length;
  const distancia = matriz.map(fila => [...fila]);

  for (let k = 0; k < n; k++) {
    for (let i = 0; i < n; i++) {
      for (let j = 0; j < n; j++) {
        if (distancia[i][k] !== Infinity &&
            distancia[k][j] !== Infinity) {
          distancia[i][j] = Math.min(
            distancia[i][j],
            distancia[i][k] + distancia[k][j]
          );
        }
      }
    }
  }
  return distancia;
}

29.8 Por qué k debe ser el bucle exterior

La demostración se apoya en que cada etapa habilita exactamente un nuevo intermediario. Por eso el bucle de k debe envolver a los bucles de i y j.

Intercambiar i y j no altera la idea, pero colocar k dentro puede usar información en un orden que rompe la recurrencia y dejar caminos sin descubrir.

29.9 Evitar operaciones con infinito

Matemáticamente, infinito más un valor finito continúa siendo infinito. En código es conveniente verificar que ambos tramos existan antes de sumarlos.

Solo evaluar D[i][k] + D[k][j] si ambos valores son finitos.

Esta comprobación es imprescindible en lenguajes donde el infinito se representa mediante un número centinela que podría desbordarse al sumarlo.

29.10 Reconstrucción de caminos

La matriz de distancias informa el costo, pero no la secuencia de vértices. Para reconstruir rutas se mantiene una matriz siguiente.

siguiente[i][j] = j si existe la arista i → j
Al mejorar pasando por k: siguiente[i][j] = siguiente[i][k]

El valor almacenado indica el primer paso que debemos tomar desde i para avanzar hacia j.

29.11 Implementación con reconstrucción

function reconstruirCamino(siguiente, origen, destino) {
  if (siguiente[origen][destino] === null) return null;

  const camino = [origen];
  let actual = origen;

  while (actual !== destino) {
    actual = siguiente[actual][destino];
    camino.push(actual);
  }
  return camino;
}

Si el par está afectado por un ciclo negativo, no debe reconstruirse como si tuviera un camino mínimo finito.

29.12 Detección de ciclos negativos

Sin ciclos negativos, la distancia mínima de un vértice hacia sí mismo es 0. Si al terminar aparece un valor negativo en la diagonal, existe un ciclo negativo.

Existe i con D[i][i] < 0 ⇒ existe un ciclo negativo.

Floyd-Warshall permite detectar ciclos negativos en cualquier componente del grafo, no solo los alcanzables desde un origen particular.

29.13 Pares afectados por ciclos negativos

Si D[k][k] es negativo, un par i, j queda afectado cuando i puede llegar a k y k puede llegar a j. En tal caso el camino puede atravesar el ciclo repetidamente y reducir su costo sin límite.

D[i][k] < ∞, D[k][k] < 0 y D[k][j] < ∞ ⇒ distancia(i,j) = −∞

Una implementación completa puede marcar esos pares con -Infinity después de ejecutar las etapas principales.

29.14 Relación con Warshall

Warshall calcula alcanzabilidad usando operaciones booleanas. Floyd-Warshall conserva la misma estructura de tres bucles, pero trabaja con costos.

AlgoritmoOperaciónResultado
WarshallOR y ANDExiste o no existe un camino
Floyd-Warshallmínimo y sumaCosto del camino mínimo

29.15 Comparación con otros algoritmos

AlgoritmoOrígenesPesos negativosTiempo
DijkstraUnoNoO((V + E) log V)
Bellman-FordUnoO(VE)
Floyd-WarshallTodosO(V³)

Para grafos dispersos grandes sin pesos negativos puede ser mejor ejecutar Dijkstra desde cada origen. Floyd-Warshall resulta atractivo para grafos densos, tamaños moderados y consultas entre todos los pares.

29.16 Complejidad

Tiempo: O(V³)
Espacio: O(V²)

Los tres bucles recorren todas las combinaciones de k, i y j. Las matrices de distancia y reconstrucción requieren espacio cuadrático.

29.17 Aplicaciones

  • Calcular distancias entre todas las ciudades de una red moderada.
  • Preprocesar consultas repetidas de rutas.
  • Analizar costos entre todos los estados de un sistema.
  • Detectar ciclos negativos en cualquier región del grafo.
  • Obtener diámetros, centros y otras medidas basadas en distancias.
  • Resolver problemas de programación dinámica sobre relaciones entre pares.

29.18 Errores comunes

  • Inicializar con cero pares que no poseen una arista.
  • Olvidar colocar cero en la diagonal.
  • Ubicar el bucle k en una posición incorrecta.
  • Sumar valores centinela de infinito sin comprobarlos.
  • Suponer que admitir pesos negativos significa ignorar ciclos negativos.
  • No actualizar la matriz de reconstrucción cuando mejora una distancia.
  • Aplicar O(V³) sin considerar el tamaño y la densidad del grafo.

29.19 Qué debes recordar de este tema

  • Floyd-Warshall calcula caminos mínimos entre todos los pares.
  • Usa una matriz y programación dinámica.
  • Su recurrencia compara D[i][j] con D[i][k] + D[k][j].
  • El vértice intermediario k debe corresponder al bucle exterior.
  • Admite pesos negativos, pero debe detectar ciclos negativos.
  • Una diagonal negativa revela un ciclo de costo negativo.
  • Su tiempo es O(V³) y su espacio O(V²).

29.20 Conclusión

Floyd-Warshall condensa el problema de todos los caminos mínimos en una recurrencia compacta. Cada etapa permite un nuevo intermediario hasta convertir la matriz inicial de aristas en una tabla completa de distancias.

En el próximo tema estudiaremos los árboles de expansión mínima, cuyo objetivo no es optimizar rutas individuales, sino conectar todos los vértices con el menor costo total.