Atlasingeniería

Diseño de algoritmosAlgoritmos en grafosTema 2

Caminos mínimos entre todos los pares

Floyd-Warshall resuelve las distancias entre todos los pares con tres ciclos anidados y una idea: permitir un vértice intermedio más por vez.

Para este tema conviene tener claro:Programación dinámica desde la recursión

Si hace falta la distancia entre todos los pares de vértices, correr Dijkstra desde cada uno es una opción razonable. Pero hay un algoritmo de tres ciclos anidados y cinco líneas que resuelve lo mismo, incluso con pesos negativos, y su idea central se explica en una frase.

Programación dinámica sobre los intermedios permitidos

Floyd-Warshall es programación dinámica sobre qué vértices se permiten como intermedios. El estado es: la mejor distancia de ii a jj usando sólo los primeros kk vértices como escalas.

Al habilitar el vértice kk, para cada par hay dos opciones: el camino no lo usa y queda igual, o lo usa y se parte en dos tramos que ya están calculados:

dk(i,j)=min(dk1(i,j),  dk1(i,k)+dk1(k,j)).d_k(i,j)=\min\big(d_{k-1}(i,j),\; d_{k-1}(i,k)+d_{k-1}(k,j)\big).

Antes de seguir, predecí

Floyd-Warshall sobre un grafo de 5000 vértices. ¿Cuánto tarda?

Tres ciclos, y un orden que no se puede alterar

Eso se escribe como tres ciclos sobre una matriz, y hay un orden que no se puede alterar: el ciclo de kk va afuera, y los de ii y jj adentro. Con kk adentro, el algoritmo mezcla estados de distintas etapas y da resultados incorrectos.

La otra sutileza es que no hace falta mantener dos matrices, una por etapa. Actualizar sobre la misma es correcto, porque la fila y la columna kk no cambian durante la iteración kk: ese detalle es lo que reduce la memoria a Θ(V2)\Theta(V^2).

Distancias directas. ∞ significa que no hay arista.

1 / 4
Habilitar C como escala mejora el camino de A a B: 1 + 2 es mejor que el 4 directo. Después, esa mejora habilita otra. Por eso el ciclo de k va afuera.

Cuándo conviene contra correr Dijkstra V veces

El costo es Θ(V3)\Theta(V^3) pase lo que pase, sin importar cuántas aristas haya. La comparación con correr Dijkstra VV veces —O(VElogV)O(V\,E\log V)— depende de la densidad: en grafos densos Floyd-Warshall gana, en ralos pierde.

Lo que siempre gana es en simplicidad y en constantes: es una matriz y tres ciclos sobre memoria contigua, sin colas de prioridad ni listas de adyacencia.

Pesos negativos y ciclos, leídos en la diagonal

Acepta pesos negativos sin cambios, cosa que Dijkstra no. Y detecta ciclos negativos con una lectura: si algún elemento de la diagonal quedó menor que cero, hay un ciclo que vuelve al mismo vértice con costo negativo.

Si hay ciclos negativos, el resto de los valores deja de tener sentido y conviene cortar ahí. La alternativa cuando el grafo es ralo y tiene negativos es Johnson: reetiqueta los pesos con Bellman-Ford para volverlos no negativos y después corre Dijkstra desde cada vértice.

Reconstruir el camino, no sólo la distancia

Para reconstruir los caminos, y no sólo las distancias, se lleva una segunda matriz con el próximo salto —o el vértice intermedio— de cada par, que se actualiza junto con la distancia.

Reconstruir un camino es después seguir esa matriz salto a salto. Cuesta lo que mide el camino, y evita guardar V2V^2 listas completas.

El mismo esquema para otras preguntas

El mismo esquema resuelve otras preguntas cambiando la operación. Con o lógico en vez de suma y mínimo, calcula la clausura transitiva: qué vértices son alcanzables desde cuáles, que es el algoritmo de Warshall.

Con máximo y mínimo da el camino de cuello de botella —la mayor capacidad mínima entre dos puntos—. Es el mismo recorrido de estados con otro semianillo, y sirve para redes de capacidad o rutas de confiabilidad.

Cuándo Floyd-Warshall y cuándo Dijkstra n veces

Floyd-WarshallDijkstra desde cada vérticeJohnson
CostoO(V³)O(V · E log V)O(V · E log V) más O(V · E)
Pesos negativosnosí, si no hay ciclos negativos
Conviene cuandoel grafo es denso o chicoel grafo es raloralo y con pesos negativos
MemoriaO(V²) siempreO(V²) para guardar el resultadoO(V²)
Códigotres ciclos anidadosun Dijkstra en un ciclorepesar y después Dijkstra
El cruce está alrededor de E ≈ V²/log V. Con un grafo ralo de mil vértices, correr Dijkstra mil veces es más rápido que Floyd-Warshall, y bastante más código.
Más a fondo · nivel seniorLa misma forma sirve para otras cosas

Los tres ciclos de Floyd-Warshall son un esquema más general: si se reemplaza el mínimo por otra operación y la suma por otra, el mismo código resuelve otros problemas.

Con OR en vez de mínimo y AND en vez de suma, calcula la clausura transitiva: qué vértices son alcanzables desde cuáles. Ese es el algoritmo de Warshall, y es el que responde «¿este módulo depende, directa o indirectamente, de aquel?».

Con máximo en vez de mínimo y mínimo en vez de suma, calcula el camino de máxima capacidad entre cada par, que es la pregunta de una red de transporte: cuál es el cuello de botella del mejor camino.

Los tres son el mismo algoritmo sobre semianillos distintos. Reconocer esa estructura es lo que convierte tres algoritmos en uno, y es la clase de observación que hace que un área se vuelva manejable en vez de una lista de recetas.

Lo que preguntan sobre esto

Cierre

Floyd-Warshall pregunta, para cada par y cada vértice nuevo habilitado, si pasar por ahí mejora. Con kk en el ciclo externo da todas las distancias en Θ(V3)\Theta(V^3) y Θ(V2)\Theta(V^2) de memoria, acepta pesos negativos, detecta ciclos negativos en la diagonal y se adapta a otras preguntas cambiando las operaciones.

Autoevaluación

¿Lo entendiste?

¿Cuál es el estado de la programación dinámica de Floyd-Warshall?
¿Por qué el ciclo de k tiene que ir afuera?
¿Por qué se puede actualizar sobre la misma matriz, sin guardar una por etapa?
Floyd-Warshall es Θ(V³) siempre. ¿Cuándo conviene frente a correr Dijkstra desde cada vértice?