Teoría de grafos y programación dinámica

El algoritmo de Floyd-Warshall explicado

Dijkstra y Bellman-Ford responden a una pregunta: ¿a qué distancia está todo desde aquí? Floyd-Warshall las responde todas a la vez. Aprende la recurrencia de programación dinámica tras sus tres bucles anidados, cómo detecta ciclos negativos gratis y cuándo supera a repetir Dijkstra.

12 min de lectura Actualizado: agosto de 2026 Nivel avanzado
Mohammed Islam Hadjoudj
Mohammed Islam Hadjoudj
Ingeniero experto en investigación de operaciones

1. Introducción al algoritmo de Floyd-Warshall

El algoritmo de Floyd-Warshall resuelve el problema de los caminos más cortos entre todos los pares. Dado un grafo dirigido y ponderado, halla la distancia más corta entre cada par de vértices, no solo las distancias desde un punto de partida elegido. Cuando termina, dispones de una matriz de distancias completa: consulta cualquier origen y cualquier destino, y la respuesta ya está ahí.

Esta es una pregunta distinta de la que responden el algoritmo de Dijkstra y el de Bellman-Ford. Aquellos son algoritmos de fuente única: les das un vértice de partida y te dicen a qué distancia está cada uno de los demás. Floyd-Warshall responde todas esas preguntas a la vez, para cada posible vértice de partida, en una sola ejecución.

Lo notable del algoritmo es lo poco que necesita para lograrlo. No hay cola de prioridad, ni conjunto de visitados, ni recursión. Todo el algoritmo son tres bucles anidados sobre una matriz, y su corrección descansa sobre una única idea clara tomada de la programación dinámica. Además admite pesos de arista negativos, algo que Dijkstra no puede, e informa de la presencia de un ciclo negativo como efecto secundario del trabajo que ya estaba haciendo.

2. Todos los pares frente a fuente única: ¿por qué no repetir Dijkstra?

Una objeción evidente es que podrías simplemente ejecutar un algoritmo de fuente única una vez desde cada vértice. Ese enfoque es legítimo y a veces preferible, así que conviene precisar cuándo gana cada uno.

Ejecutar el algoritmo de Dijkstra desde los V vértices, con un montículo binario, cuesta O(V * E log V). En un grafo disperso, donde el número de aristas E se acerca a V, eso es aproximadamente O(V2 log V), que supera cómodamente a Floyd-Warshall. En un grafo denso, donde E se aproxima a V2, esa misma repetición cuesta cerca de O(V3 log V), y la cota constante O(V3) de Floyd-Warshall resulta mejor.

Dos consideraciones adicionales suelen zanjar la elección antes que la complejidad:

Regla práctica: elige Floyd-Warshall para grafos densos, para grafos con aristas negativas o cuando realmente necesites todos los pares. Elige Dijkstra repetido para grafos grandes y dispersos con pesos no negativos.

3. La idea central: vértices intermedios

La intuición detrás de Floyd-Warshall consiste en restringir el problema de un modo que facilite hacerlo crecer. En lugar de preguntar de entrada "¿cuál es el camino más corto de i a j?", plantea una pregunta más estrecha:

¿Cuál es el camino más corto de i a j que solo puede pasar, como escalas intermedias, por los vértices de cierto conjunto permitido?

Numera los vértices de 1 a V. Define el conjunto permitido como los primeros k vértices y escribe dk(i, j) para la distancia más corta de i a j usando solo {1, 2, ..., k} como vértices intermedios. Los extremos i y j siempre están permitidos, caigan o no dentro del conjunto permitido. Solo están restringidos los vértices estrictamente intermedios.

Los dos extremos de esta definición son reveladores. Cuando k = 0 nada puede servir de escala, así que d0(i, j) es justamente el peso de la arista directa de i a j, o infinito si no existe tal arista. Cuando k = V todos los vértices están permitidos, así que dV(i, j) es la verdadera distancia más corta sin restricciones. El algoritmo es la maquinaria que lleva de la primera a la segunda.

4. La relación de recurrencia

Supón que ya conoces todos los valores de dk-1 y quieres dk. Considera el camino más corto de i a j que puede usar {1, ..., k}. Hay exactamente dos posibilidades, y son mutuamente excluyentes:

  1. El camino no usa el vértice k. Entonces solo usa {1, ..., k-1}, así que su longitud es dk-1(i, j), un valor que ya tienes.
  2. El camino sí usa el vértice k. Como un camino más corto nunca repite un vértice, pasa por k exactamente una vez. Eso lo divide en un tramo de i a k y otro de k a j, y ninguno de los dos puede usar k como escala. Su longitud es entonces dk-1(i, k) + dk-1(k, j), y ambos términos también son ya conocidos.

El camino más corto es el menor de los dos, lo que da la recurrencia que está en el corazón del algoritmo:

d[k][i][j] = min( d[k-1][i][j],
                  d[k-1][i][k] + d[k-1][k][j] )

En palabras: pasar por k merece la pena solo si el rodeo hasta k y de ahí a j es más corto que la mejor ruta hallada sin él. Esto es programación dinámica en estado puro. Cada subproblema se resuelve una vez, se almacena y se reutiliza.

En la práctica nadie almacena V matrices separadas. Los valores pueden actualizarse in situ en una única matriz V x V, porque durante la iteración k las entradas d(i, k) y d(k, j) no pueden cambiar: actualizar cualquiera de ellas exigiría d(k, k), que vale 0 mientras el grafo no tenga un ciclo negativo. Leer un valor ya sobrescrito en esa misma ronda es por tanto inofensivo, y el consumo de memoria baja de O(V3) a O(V2).

Comparación de la distancia directa de i a j con la distancia a través del vértice intermedio k.
Cada paso plantea una sola pregunta: ¿es la ruta a través del vértice k más corta que la mejor hallada hasta ahora?

5. Ejecución paso a paso

Las recurrencias abstractas se aclaran en cuanto se ejecutan con números. Tomemos un grafo dirigido de cuatro vértices con estas aristas:

Inicializa la matriz solo a partir de las aristas. La diagonal es 0, porque cada vértice se alcanza a sí mismo sin coste, y toda arista ausente es infinito.

k = 0 (solo aristas directas)

        1     2     3     4
  1     0     5     inf   10
  2     inf   0     3     inf
  3     inf   inf   0     1
  4     inf   inf   inf   0

Ronda k = 1. El vértice 1 queda ahora permitido como escala. Cualquier actualización necesitaría un d(i, 1) finito, pero la columna 1 es infinita en todas partes salvo en la diagonal, porque ninguna arista llega al vértice 1. Nada cambia.

Ronda k = 2. El vértice 2 pasa a estar disponible. La fila 2 ofrece un d(2, 3) = 3 finito, y la columna 2 ofrece un d(1, 2) = 5 finito. De ahí sale una mejora candidata:

Ronda k = 3. El vértice 3 pasa a estar disponible, y d(3, 4) = 1. Mejoran dos entradas:

Fíjate en que la mejora de d(1, 4) se apoyó en d(1, 3) = 8, un valor descubierto en la ronda anterior. El algoritmo construye caminos largos a partir de caminos cortos que ya ha demostrado.

Ronda k = 4. El vértice 4 no tiene aristas salientes, así que la fila 4 es infinita salvo en la diagonal y ningún camino puede pasar útilmente por él. Nada cambia, y el algoritmo termina.

Resultado (todos los pares)

        1     2     3     4
  1     0     5     8     9
  2     inf   0     3     4
  3     inf   inf   0     1
  4     inf   inf   inf   0

La respuesta para 1 -> 4 es 9, por la ruta 1 -> 2 -> 3 -> 4 con un coste de 5 + 3 + 1, que supera a la arista directa de peso 10. Los infinitos restantes son correctos, no inacabados: ninguna arista entra al vértice 1, así que nada puede alcanzarlo.

6. Implementación y pseudocódigo

El algoritmo es lo bastante breve como para memorizarlo. El detalle que más importa, por encima de cualquier otro, es el orden de los bucles.

function FloydWarshall(W, V):
    // W[i][j] = peso de la arista i -> j, o Infinity si no existe
    // dist es una matriz V x V

    for i from 1 to V:
        for j from 1 to V:
            dist[i][j] = W[i][j]
        dist[i][i] = 0

    // k DEBE ser el bucle más externo
    for k from 1 to V:
        for i from 1 to V:
            for j from 1 to V:
                if dist[i][k] + dist[k][j] < dist[i][j]:
                    dist[i][j] = dist[i][k] + dist[k][j]

    return dist

k debe ser el bucle más externo. Esta es, con diferencia, la forma más común de escribir mal el algoritmo. La recurrencia exige que todo par (i, j) se actualice respecto al vértice intermedio k antes de pasar a k + 1. Si k se coloca en un bucle interno, la matriz se rellena en un orden que carece de significado, y el resultado son distancias que parecen plausibles pero no son óptimas.

Una advertencia de implementación: si representas el infinito con un valor centinela grande como INT_MAX en lugar de un infinito real en coma flotante, dist[i][k] + dist[k][j] puede desbordarse y dar la vuelta hasta un número negativo, que la comparación aceptará encantada. Usa un infinito de verdad o protege la suma omitiendo la actualización cuando alguno de los operandos sea el centinela.

7. Complejidad temporal y espacial

La insensibilidad a E es el rasgo que lo define. Un grafo con cuatro vértices y tres aristas cuesta lo mismo que uno con cuatro vértices y doce. Eso resulta derrochador en grafos dispersos y perfectamente eficiente en los densos. A cambio, la constante es muy pequeña y el patrón de acceso a memoria es regular y aprovecha bien la caché, de modo que Floyd-Warshall suele rendir mejor de lo que sugiere su asintótica en grafos de unos pocos cientos de vértices.

8. Aristas negativas y detección de ciclos negativos

Floyd-Warshall acepta pesos de arista negativos sin modificación alguna. Su recurrencia nunca supone que alargar un camino incremente su longitud, y esa suposición es precisamente la que hace fallar al algoritmo de Dijkstra con entradas negativas.

Los ciclos negativos son otra cuestión, y ningún algoritmo puede devolver distancias mínimas con sentido en su presencia: puedes dar vueltas al ciclo indefinidamente y hundir el coste sin límite. Lo que Floyd-Warshall te ofrece es una forma de advertirlo gratis. Inspecciona la diagonal cuando el algoritmo termine:

for i from 1 to V:
    if dist[i][i] < 0:
        informar "ciclo negativo detectado"

La diagonal se inicializó a 0. Un vértice solo puede acabar con una distancia negativa a sí mismo si existe un recorrido cerrado que empieza y termina en él cuyo peso total está por debajo de cero, que es exactamente la definición de ciclo negativo. Donde Bellman-Ford necesita una pasada adicional específica sobre todas las aristas para determinar lo mismo, a Floyd-Warshall le basta con echar un vistazo a V entradas que ya ha calculado.

Ten en cuenta el alcance de la comprobación. Señala cualquier ciclo negativo en el que participe el vértice correspondiente. Si la diagonal está limpia, toda distancia de la matriz es fiable. Si no lo está, los valores finitos del resto de la matriz deben tratarse como carentes de sentido, no como meramente imprecisos.

9. Reconstruir los caminos reales

La matriz de distancias registra a qué distancia están dos vértices, pero no qué ruta lo consigue. Recuperar la ruta exige una matriz adicional, y el esquema más barato guarda, para cada par, el siguiente vértice del camino.

Inicializa next[i][j] = j siempre que exista una arista directa, y déjalo nulo en caso contrario. Después, cada vez que el bucle principal mejore dist[i][j] pasando por k, hereda el primer paso de la nueva ruta:

if dist[i][k] + dist[k][j] < dist[i][j]:
    dist[i][j] = dist[i][k] + dist[k][j]
    next[i][j] = next[i][k]

La asignación es next[i][k], no k. El primer movimiento del trayecto mejorado de i a j es el primer movimiento del trayecto de i a k, que bien puede ser un vértice completamente distinto. Leer el camino es entonces un paseo corto: empieza en i, sigue next repetidamente y detente al llegar a j. Esto cuesta O(V2) de espacio adicional y ningún tiempo apreciable.

10. Variantes y aplicaciones reales

La estructura de tres bucles se generaliza mucho más allá de los caminos mínimos, porque la recurrencia solo necesita una operación que combine dos tramos y otra que elija entre alternativas.

Clausura transitiva (algoritmo de Warshall)

Sustituye la suma por el Y lógico y el mínimo por el O lógico, y los mismos bucles calculan la alcanzabilidad: si existe o no un camino entre cada par, al margen del coste. Este es el resultado original de Warshall de 1962, y es la razón de que el algoritmo combinado lleve ambos nombres. Aparece en el análisis de flujo de datos de los compiladores, en la resolución de dependencias y en la planificación de consultas de bases de datos.

Camino más ancho y problemas de cuello de botella

Sustituye la suma por el mínimo y el mínimo por el máximo, y el algoritmo halla la ruta cuyo enlace más estrecho es lo más ancho posible. Es la formulación natural para el enrutamiento de máximo ancho de banda en una red y para la planificación de capacidad en logística.

Enrutamiento de redes y matrices de latencia

Los operadores de red necesitan a menudo una matriz completa de latencias o de saltos entre todos los pares de nodos de una topología. Las topologías troncales suelen ser densas y de un número moderado de vértices, que es justo el régimen para el que se creó Floyd-Warshall.

Arbitraje de divisas

Modela las divisas como vértices y los tipos de cambio como aristas. Tomar el logaritmo negativo de cada tipo convierte la multiplicación de tipos en suma de pesos, y un bucle de arbitraje rentable se convierte en un ciclo negativo. La comprobación de la diagonal informa entonces de si existe una oportunidad de arbitraje, y la matriz next reconstruye la secuencia de operaciones.

11. Recursos académicos e historia

La atribución del algoritmo es inusualmente enrevesada. Varios investigadores llegaron a los mismos tres bucles de forma independiente en pocos años.

Para un tratamiento riguroso con demostraciones completas de corrección, la referencia estándar es Cormen, Leiserson, Rivest y Stein, Introduction to Algorithms, en el capítulo sobre caminos mínimos entre todos los pares. Quien compare enfoques para grafos dispersos debería estudiar además el algoritmo de Johnson, que repondera un grafo de modo que repetir Dijkstra siga siendo válido incluso con aristas negativas. Las citas completas figuran al final de este artículo.

Preguntas frecuentes

¿Cuándo debo usar Floyd-Warshall en lugar del algoritmo de Dijkstra?

Usa Floyd-Warshall cuando necesites la distancia más corta entre cada par de vértices, cuando el grafo sea denso o cuando haya pesos de arista negativos. Ejecutar el algoritmo de Dijkstra desde cada vértice cuesta O(V * E log V), que es más rápido en grafos grandes y dispersos pero incorrecto si alguna arista es negativa. El tiempo O(V^3) de Floyd-Warshall no depende del número de aristas, así que gana en grafos densos.

¿Puede Floyd-Warshall manejar pesos de arista negativos?

Sí. Floyd-Warshall acepta pesos negativos sin modificación, porque su recurrencia nunca supone que alargar un camino incremente su longitud. No puede devolver distancias con sentido cuando existe un ciclo negativo, pero detecta ese caso gratis: tras la ejecución, todo vértice cuya distancia a sí mismo sea inferior a cero está en un ciclo negativo.

¿Por qué el bucle k debe ser el más externo?

La recurrencia exige que todo par (i, j) se actualice respecto al vértice intermedio k antes de pasar a k + 1. Colocar k en un bucle interno rellena la matriz en un orden sin significado y produce distancias que parecen plausibles pero no son óptimas. Es, con diferencia, la forma más común de implementar mal el algoritmo.

Observa cómo se llena la matriz de distancias

Tres bucles anidados son difíciles de imaginar y fáciles de ver. Ejecuta Floyd-Warshall y sigue cómo se resuelve cada par.

Abrir la calculadora Floyd-Warshall

Referencias verificadas y lecturas adicionales