Fundamentos

Dijkstra vs Bellman-Ford vs Floyd-Warshall

Dijkstra, Bellman-Ford y Floyd-Warshall encuentran caminos más cortos, pero responden a preguntas distintas en condiciones distintas. Esta guía ejecuta los tres sobre un mismo grafo de ejemplo con una arista negativa, muestra exactamente por qué Dijkstra falla en él, explica cómo los otros dos detectan ciclos negativos, compara su velocidad con recuentos de operaciones medidos y trata el algoritmo de Johnson y los casos especiales en los que gana un método más sencillo, con código Python probado en todo momento.

22 Min de lectura Actualizado: Octubre 2026 Nivel Principiante
Mohammed Islam Hadjoudj
Mohammed Islam Hadjoudj
Expert Operations Research Engineer

1. La respuesta corta

El algoritmo de Dijkstra, el algoritmo de Bellman-Ford y el algoritmo de Floyd-Warshall encuentran caminos más cortos en un grafo ponderado, pero responden a preguntas distintas en condiciones distintas. Dos preguntas deciden entre ellos: ¿necesitas distancias desde un único origen o entre todos los pares de vértices? y ¿puede una arista tener un peso negativo?

DijkstraBellman-FordFloyd-Warshall
Qué calculaDe un origen a todos los vérticesDe un origen a todos los vérticesEntre todos los pares de vértices
Aristas con peso negativoNo: da respuestas erróneasSíSí
Detecta ciclos negativosNoSíSí
TiempoO((V + E) log V) con un montículo binarioO(V E)O(V3)
MemoriaO(V)O(V)O(V2)
EstrategiaVoraz: cierra el vértice más cercanoRepetición: relaja todas las aristasProgramación dinámica sobre escalas
Úsalo cuandoLos pesos son no negativos, lo que cubre la mayoría de los mapas de carreteras, redes y videojuegosAlgunos pesos son negativos o hay que detectar un ciclo negativoNecesitas todas las distancias en un grafo pequeño o denso

Aquí V es el número de vértices y E el número de aristas. En una frase: usa Dijkstra por defecto, cambia a Bellman-Ford cuando los pesos puedan ser negativos, y usa Floyd-Warshall cuando necesites la tabla de distancias completa de un grafo con a lo sumo unos pocos cientos de vértices. Cuando necesites todos los pares en un grafo grande y disperso con aristas negativas, el algoritmo de Johnson de la sección 7 combina los dos primeros. El resto de esta guía ejecuta los tres sobre el mismo grafo y muestra dónde gana y dónde falla cada uno.

Un diagrama de decisión. Primera pregunta: ¿necesitas todos los pares o un solo origen? Para un solo origen: ¿pueden ser negativos los pesos? Si no, Dijkstra; si sí, Bellman-Ford. Para todos los pares: ¿pueden ser negativos los pesos y es denso el grafo? Los grafos densos o pequeños usan Floyd-Warshall; los grafos dispersos usan Dijkstra desde cada vértice cuando los pesos son no negativos y el algoritmo de Johnson cuando no lo son. Notas al margen: los grafos no ponderados solo necesitan una búsqueda en anchura, y los grafos acíclicos se pueden resolver en tiempo lineal en orden topológico.
Dos preguntas eligen el algoritmo: un origen o todos los pares, y si pueden aparecer pesos negativos. La densidad decide entre las opciones para todos los pares.

2. Un grafo, tres respuestas

El ejemplo que nos acompaña es un pequeño grafo dirigido con seis vértices, siete aristas y una arista negativa, B → A con peso -4. Piensa en los pesos como costes: un coste negativo es un reembolso, como una carretera de peaje que te paga por usarla, o un paso de un proceso químico que libera energía.

S → A  3      S → B  6      A → C  4      B → A  -4
C → T  2      B → D  3      D → T  4

6 vértices, 7 aristas, sin ciclos; origen S

La ruta más barata de S a T es S, B, A, C, T, con coste 6 - 4 + 4 + 2 = 8. Empieza por el primer paso más caro, hacia B, y lo recupera con el reembolso. El programa siguiente implementa los tres algoritmos en su forma estándar de libro de texto y los ejecuta desde S.

import heapq
INF = float('inf')

def graph(edges):
    adj = {}
    for u, v, w in edges:
        adj.setdefault(u, []).append((v, w))
        adj.setdefault(v, [])
    return adj

def dijkstra(adj, s):
    """Voraz: cierra el vértice abierto más cercano y no vuelve a visitarlo."""
    dist = {v: INF for v in adj}
    dist[s] = 0
    done, heap = set(), [(0, s)]
    while heap:
        d, u = heapq.heappop(heap)
        if u in done:
            continue                      # entrada obsoleta del montículo
        done.add(u)
        for v, w in adj[u]:
            if v not in done and d + w < dist[v]:
                dist[v] = d + w
                heapq.heappush(heap, (dist[v], v))
    return dist

def bellman_ford(adj, s):
    """Relaja todas las aristas, ronda tras ronda, hasta que nada cambie."""
    dist = {v: INF for v in adj}
    dist[s] = 0
    for _ in range(len(adj) - 1):
        changed = False
        for u in adj:
            for v, w in adj[u]:
                if dist[u] + w < dist[v]:
                    dist[v] = dist[u] + w
                    changed = True
        if not changed:
            break
    return dist

def floyd_warshall(adj):
    """Todos los pares: admite los vértices como escalas de uno en uno."""
    d = {u: {v: 0 if u == v else INF for v in adj} for u in adj}
    for u in adj:
        for v, w in adj[u]:
            d[u][v] = min(d[u][v], w)
    for k in adj:
        for i in adj:
            for j in adj:
                if d[i][k] + d[k][j] < d[i][j]:
                    d[i][j] = d[i][k] + d[k][j]
    return d

G = graph([('S', 'A', 3), ('S', 'B', 6), ('A', 'C', 4), ('B', 'A', -4),
           ('C', 'T', 2), ('B', 'D', 3), ('D', 'T', 4)])
dj, bf, fw = dijkstra(G, 'S'), bellman_ford(G, 'S'), floyd_warshall(G)['S']
for v in G:
    print(v, dj[v], bf[v], fw[v], '' if dj[v] == bf[v] else '<- Dijkstra is wrong')
# S 0 0 0
# A 3 2 2 <- Dijkstra is wrong
# B 6 6 6
# C 7 6 6 <- Dijkstra is wrong
# T 9 8 8 <- Dijkstra is wrong
# D 9 9 9
Izquierda: el grafo de ejemplo con los vértices S, A, B, C, D y T, la arista negativa de B a A dibujada en rojo y la ruta más corta S, B, A, C, T en verde. Derecha: una tabla de las distancias desde S. Las distancias verdaderas son S 0, A 2, B 6, C 6, D 9 y T 8. Bellman-Ford y Floyd-Warshall coinciden con todas. Dijkstra da A 3, C 7 y T 9, tres valores erróneos marcados en rojo.
En un grafo con una sola arista negativa, Dijkstra se equivoca en tres de las seis distancias. Bellman-Ford y Floyd-Warshall coinciden con los valores verdaderos.

Bellman-Ford y Floyd-Warshall devuelven las distancias verdaderas. Dijkstra da 3 para A, 7 para C y 9 para T, todas demasiado altas, y no lanza ningún error: las respuestas erróneas parecen exactamente correctas. La sección 4 explica por qué. En un grafo con pesos no negativos los tres coincidirían, y la única diferencia sería la velocidad.

3. Qué hace cada algoritmo

Los tres algoritmos se construyen a partir de una sola operación, la relajación. Cada vértice v lleva una distancia provisional dist[v], que empieza en infinito, o en 0 para el origen. Relajar la arista u → v con peso w consiste en preguntar si pasar por u es más barato y, si lo es, anotarlo:

if dist[u] + w < dist[v]:
    dist[v] = dist[u] + w

Una distancia provisional solo puede bajar, y siempre es el coste de alguna ruta real. Los algoritmos solo se diferencian en qué aristas relajan y en qué orden.

Izquierda: la ejecución de Dijkstra sobre el ejemplo como una línea temporal. Cierra S en 0 y después A en 3, que se declara definitivo; cuando se cierra B en 6, la ruta más barata de B a A, de coste 2, llega demasiado tarde, así que C se cierra en 7 y T en 9. Derecha: las rondas de Bellman-Ford como una tabla. La ronda 1 da A 2, B 6, C 7, D 9 y T 9; la ronda 2 baja C a 6 y T a 8; la ronda 3 no cambia nada, así que el algoritmo se detiene.
Dijkstra se compromete con A a coste 3 antes de haber visto el reembolso. Bellman-Ford sigue relajando todas las aristas hasta que una ronda entera no cambia nada.

En el ejemplo, Bellman-Ford recorre las aristas en el orden en que se listaron. Su primera ronda ya baja A a 2 cuando llega a las aristas que salen de B, pero para entonces ya ha usado el valor antiguo 3 para fijar C = 7. La segunda ronda traslada la mejora a C = 6 y T = 8. La tercera ronda no cambia nada, así que el algoritmo se detiene antes de tiempo en lugar de ejecutar las V - 1 = 5 rondas. Cuántas rondas hacen falta depende del orden de las aristas, pero nunca son más de V - 1.

4. Por qué Dijkstra falla con aristas negativas

La velocidad de Dijkstra procede de una promesa: cuando un vértice sale de la cola de prioridad, su distancia es definitiva. La promesa se apoya en un argumento que necesita pesos no negativos. Supón que u es el vértice abierto más cercano. Cualquier otra ruta hacia u tiene que salir de la región cerrada a través de algún vértice abierto x, que ya está al menos tan lejos como u, y el resto de la ruta solo puede sumar coste. Con una arista negativa, el resto de la ruta puede restar coste, y el argumento se viene abajo.

Eso es exactamente lo que ocurre en el ejemplo. Dijkstra cierra S en 0 y después A en 3, porque 3 es menor que el valor de B, que es 6. Cuando B se cierra y su arista hacia A ofrece 6 - 4 = 2, A ya es definitivo y la mejora se descarta. Todo lo construido sobre A hereda el error: C se cierra en 3 + 4 = 7 en lugar de 6, y T en 9 en lugar de 8.

Dos arreglos tentadores no funcionan:

Con pesos no negativos no surge nada de esto, y Dijkstra es correcto y el más rápido de los tres. Los pesos cero no son ningún problema: el argumento solo necesita que ninguna arista reduzca el coste.

5. Ciclos negativos

Un ciclo negativo es un ciclo cuyos pesos suman menos de cero. Si se puede alcanzar desde el origen y puede alcanzar el destino, no existe ninguna ruta más corta: dar una vuelta más al ciclo siempre sale más barato. La distancia es menos infinito, y un algoritmo debería informar del ciclo en lugar de dar un número. Añade la arista C → B con peso -5 al ejemplo y el ciclo B, A, C, B cuesta -4 + 4 - 5 = -5.

INF = float('inf')

def graph(edges):
    adj = {}
    for u, v, w in edges:
        adj.setdefault(u, []).append((v, w))
        adj.setdefault(v, [])
    return adj

def negative_cycle(adj):
    """Bellman-Ford desde un origen virtual unido a cada vértice con coste 0.
    Devuelve un ciclo negativo como lista de vértices, o None."""
    dist = {v: 0 for v in adj}
    parent = {v: None for v in adj}
    for _ in range(len(adj)):                  # una ronda más de lo habitual
        last = None
        for u in adj:
            for v, w in adj[u]:
                if dist[u] + w < dist[v]:
                    dist[v], parent[v], last = dist[u] + w, u, v
        if last is None:
            return None                        # una ronda sin cambios: no hay ciclo negativo
    for _ in range(len(adj)):                  # retrocede lo suficiente para caer en el ciclo
        last = parent[last]
    cycle, v = [last], parent[last]
    while v != last:                           # sigue los padres una vuelta al ciclo
        cycle.append(v)
        v = parent[v]
    cycle.reverse()                            # los padres apuntan hacia atrás
    return cycle + cycle[:1]

def floyd_warshall_diagonal(adj):
    """Vértices cuya distancia a sí mismos resulta negativa."""
    d = {u: {v: 0 if u == v else INF for v in adj} for u in adj}
    for u in adj:
        for v, w in adj[u]:
            d[u][v] = min(d[u][v], w)
    for k in adj:
        for i in adj:
            for j in adj:
                if d[i][k] + d[k][j] < d[i][j]:
                    d[i][j] = d[i][k] + d[k][j]
    return [v for v in adj if d[v][v] < 0]

edges = [('S', 'A', 3), ('S', 'B', 6), ('A', 'C', 4), ('B', 'A', -4),
         ('C', 'T', 2), ('B', 'D', 3), ('D', 'T', 4)]
print(negative_cycle(graph(edges)))
# None
bad = graph(edges + [('C', 'B', -5)])          # B -> A -> C -> B cuesta -4 + 4 - 5 = -5
print(negative_cycle(bad))
# ['C', 'B', 'A', 'C']
print(floyd_warshall_diagonal(bad))
# ['A', 'B', 'C']

La función inicia Bellman-Ford desde un origen virtual unido a cada vértice con coste 0, así que encuentra un ciclo negativo en cualquier parte del grafo, no solo uno alcanzable desde S. En el ejemplo original, una ronda acaba por no cambiar nada y la función devuelve None. Con la arista extra devuelve el ciclo C, B, A, C, el mismo ciclo escrito desde otro inicio, y Floyd-Warshall marca A, B y C en su diagonal. En pruebas con miles de grafos aleatorios, la diagonal señaló un ciclo negativo exactamente cuando existía uno, pero no siempre marcó todos los vértices que pueden alcanzarlo, así que úsala como una prueba de sí o no.

Hay una trampa en la que cae mucha gente. En un grafo no dirigido cada arista puede recorrerse en ambos sentidos, así que una sola arista con peso negativo ya es un ciclo negativo: u, v, u. Por tanto, Bellman-Ford ejecutado sobre la versión dirigida de un grafo no dirigido informará de un ciclo negativo siempre que alguna arista sea negativa, un escollo que detalla el libro Algorithms de Sedgewick y Wayne. Los caminos simples más cortos con pesos negativos en grafos no dirigidos requieren métodos distintos y más complejos.

6. Velocidad: cuál es más rápido y cuándo

Para un solo origen, el orden está claro siempre que Dijkstra sea aplicable: O((V + E) log V) con un montículo binario frente al O(V E) de Bellman-Ford. En un grafo con 10.000 vértices y 50.000 aristas, eso supone aproximadamente un millón de pasos de montículo frente a hasta 500 millones de relajaciones de aristas. Floyd-Warshall no es un método desde un único origen, y ejecutarlo para un solo origen desperdicia casi todo su trabajo.

Para todos los pares la comparación está más reñida, porque las alternativas consisten en ejecutar un algoritmo desde un único origen a partir de cada vértice:

Método para todos los paresTiempoAristas negativasIdeal para
Dijkstra desde cada vérticeO(V (V + E) log V)NoGrafos dispersos, pesos no negativos
Bellman-Ford desde cada vérticeO(V2 E)SíCasi nunca
Floyd-WarshallO(V3)SíGrafos densos o pequeños
Algoritmo de JohnsonO(V E + V (V + E) log V)SíGrafos dispersos con aristas negativas

Las cotas asintóticas ocultan constantes, así que el programa siguiente cuenta los pasos básicos que cada método realiza de verdad en dos grafos dirigidos aleatorios con 100 vértices: uno disperso con 300 aristas y uno denso con 4950, la mitad de todas las aristas posibles. Para Dijkstra cuenta por separado las comprobaciones de aristas y las operaciones del montículo, ya que una operación del montículo cuesta unos log V pasos.

import heapq, random
INF = float('inf')

def random_graph(n, m, seed):
    """n vértices, m aristas dirigidas distintas con pesos 1..100."""
    rnd = random.Random(seed)
    pairs = rnd.sample([(u, v) for u in range(n) for v in range(n) if u != v], m)
    adj = {v: [] for v in range(n)}
    for u, v in pairs:
        adj[u].append((v, rnd.randint(1, 100)))
    return adj

def all_pairs_dijkstra(adj):
    """Dijkstra desde cada vértice. Cuenta comprobaciones de aristas y operaciones del montículo."""
    checks = heap_ops = 0
    for s in adj:
        dist, heap = {v: INF for v in adj}, [(0, s)]
        dist[s] = 0
        while heap:
            d, u = heapq.heappop(heap)
            heap_ops += 1
            if d > dist[u]:
                continue
            for v, w in adj[u]:
                checks += 1
                if d + w < dist[v]:
                    dist[v] = d + w
                    heapq.heappush(heap, (dist[v], v))
                    heap_ops += 1
    return checks, heap_ops

def all_pairs_bellman_ford(adj):
    """Bellman-Ford desde cada vértice, con parada anticipada cuando una ronda no cambia nada."""
    m, checks = sum(len(a) for a in adj.values()), 0
    for s in adj:
        dist = {v: INF for v in adj}
        dist[s] = 0
        for _ in range(len(adj) - 1):
            changed = False
            for u in adj:
                for v, w in adj[u]:
                    if dist[u] + w < dist[v]:
                        dist[v], changed = dist[u] + w, True
            checks += m
            if not changed:
                break
    return checks

n = 100
for name, m in [('sparse', 3 * n), ('dense', n * (n - 1) // 2)]:
    adj = random_graph(n, m, seed=1)
    checks, heap_ops = all_pairs_dijkstra(adj)
    print(f'{name:6} E={m:4}  Dijkstra x V: {checks:7,} checks + {heap_ops:6,} heap ops   '
          f'Bellman-Ford x V: {all_pairs_bellman_ford(adj):9,}   Floyd-Warshall: {n ** 3:,}')
# sparse E= 300  Dijkstra x V:  27,565 checks + 21,388 heap ops   Bellman-Ford x V:   226,200   Floyd-Warshall: 1,000,000
# dense  E=4950  Dijkstra x V: 495,000 checks + 64,770 heap ops   Bellman-Ford x V: 2,712,600   Floyd-Warshall: 1,000,000
Un diagrama de barras en escala logarítmica de los pasos necesarios para calcular las distancias entre todos los pares en grafos aleatorios con 100 vértices. En el grafo disperso con 300 aristas: Dijkstra desde cada vértice unos 49.000 pasos, Bellman-Ford desde cada vértice 226.200, Floyd-Warshall 1.000.000. En el grafo denso con 4950 aristas: Dijkstra desde cada vértice unos 560.000, Bellman-Ford 2.712.600, Floyd-Warshall 1.000.000.
Trabajo para todos los pares con 100 vértices. En el grafo disperso, Dijkstra desde cada vértice es unas 20 veces más barato que Floyd-Warshall; en el grafo denso, los dos quedan dentro de un factor de dos.

En el grafo disperso, Dijkstra desde cada vértice necesita unos 49.000 pasos frente al 1.000.000 fijo de Floyd-Warshall, un factor de unos 20. En el grafo denso la diferencia casi desaparece y, una vez que se cuenta el coste logarítmico de cada operación del montículo, el sencillo bucle interno de Floyd-Warshall, que recorre la memoria en orden y no necesita cola de prioridad, suele ganar en la práctica. Bellman-Ford desde cada vértice es el más lento en ambos casos, incluso con parada anticipada. La regla práctica: por debajo de unos pocos cientos de vértices, o cuando la mayoría de los pares de vértices están unidos por una arista, usa Floyd-Warshall por su sencillez; en grafos grandes y dispersos, usa Dijkstra o Johnson desde cada vértice.

7. Todos los pares con aristas negativas: el algoritmo de Johnson

El algoritmo de Donald Johnson, de 1977, aprovecha lo mejor de ambos. Ejecuta Bellman-Ford una vez para calcular un número h(v) para cada vértice, la distancia desde un origen virtual unido a todos los vértices con coste 0. Después sustituye cada peso por

w'(u, v) = w(u, v) + h(u) - h(v)

Como h cumple h(v) ≤ h(u) + w(u, v) para toda arista, todos los pesos nuevos son no negativos. Y a lo largo de cualquier ruta de s a t los términos se cancelan de forma telescópica, así que el coste de toda ruta cambia en la misma cantidad, h(s) - h(t). Las rutas más cortas siguen siendo las más cortas, y ahora se puede ejecutar Dijkstra desde cada vértice. A diferencia de sumar una constante, este repesado no depende de cuántas aristas tenga una ruta.

import heapq
INF = float('inf')

def graph(edges):
    adj = {}
    for u, v, w in edges:
        adj.setdefault(u, []).append((v, w))
        adj.setdefault(v, [])
    return adj

def dijkstra(adj, s):
    dist, heap = {v: INF for v in adj}, [(0, s)]
    dist[s] = 0
    while heap:
        d, u = heapq.heappop(heap)
        if d > dist[u]:
            continue
        for v, w in adj[u]:
            if d + w < dist[v]:
                dist[v] = d + w
                heapq.heappush(heap, (dist[v], v))
    return dist

def johnson(adj):
    """Todos los pares con aristas negativas: un Bellman-Ford y luego Dijkstra desde cada vértice."""
    h = {v: 0 for v in adj}                    # distancias desde un origen virtual
    for _ in range(len(adj)):
        for u in adj:
            for v, w in adj[u]:
                h[v] = min(h[v], h[u] + w)
    for u in adj:
        for v, w in adj[u]:
            if h[u] + w < h[v]:
                raise ValueError('negative cycle')
    # repesado: toda arista pasa a ser no negativa, todo camino cambia en h[s] - h[t]
    lifted = {u: [(v, w + h[u] - h[v]) for v, w in adj[u]] for u in adj}
    return {s: {t: d - h[s] + h[t] for t, d in dijkstra(lifted, s).items()} for s in adj}, lifted

G = graph([('S', 'A', 3), ('S', 'B', 6), ('A', 'C', 4), ('B', 'A', -4),
           ('C', 'T', 2), ('B', 'D', 3), ('D', 'T', 4)])
dist, lifted = johnson(G)
print({u: [(v, w) for v, w in lifted[u]] for u in ('S', 'A', 'B')})
# {'S': [('A', 7), ('B', 6)], 'A': [('C', 0)], 'B': [('A', 0), ('D', 3)]}
print(dist['S'])
# {'S': 0, 'A': 2, 'B': 6, 'C': 6, 'T': 8, 'D': 9}
print(dist['B'])
# {'S': inf, 'A': -4, 'B': 0, 'C': 0, 'T': 2, 'D': 3}

En el ejemplo, h(A) = -4 y todos los demás vértices reciben 0. La arista negativa B → A pasa a valer 0, S → A sube de 3 a 7 y A → C baja de 4 a 0, y Dijkstra con los nuevos pesos, corregido de vuelta con h, da las distancias verdaderas desde cada origen, incluida la fila de B con su entrada negativa -4. Si Bellman-Ford encuentra un ciclo negativo en el primer paso, el algoritmo de Johnson se detiene e informa de él.

8. Cuando ninguno de los tres es la mejor opción

Algunos grafos tienen una estructura que un método más sencillo o más rápido puede aprovechar:

9. Memoria y recuperación de los caminos

A menudo las distancias no bastan: quieres la ruta en sí. Dijkstra y Bellman-Ford guardan un padre por vértice, el vértice desde el que llegó la mejor ruta, y la ruta hasta t se lee hacia atrás desde t. Eso cuesta O(V) de memoria adicional. Floyd-Warshall necesita una tabla completa de V por V con entradas de "siguiente salto" junto a su tabla de distancias, así que su memoria es O(V2). Con 10.000 vértices y entradas de 8 bytes, solo la tabla de distancias ocupa unos 800 megabytes, y la tabla de siguientes saltos añade otro tanto. Más que el tiempo, lo que suele descartar Floyd-Warshall en grafos grandes es la memoria.

10. Dónde se usa cada uno

11. Un área de investigación todavía activa

Los caminos más cortos son uno de los problemas más antiguos de la algoritmia, y las cotas siguen moviéndose. En 2022, Aaron Bernstein, Danupon Nanongkai y Christian Wulff-Nilsen demostraron que los caminos más cortos desde un único origen con pesos enteros negativos se pueden resolver en tiempo casi lineal, muy por debajo del O(V E) de Bellman-Ford. En 2024, Bernhard Haeupler y sus coautores demostraron que el algoritmo de Dijkstra con un montículo diseñado adecuadamente es universalmente óptimo para ordenar los vértices por distancia: en cada grafo fijo, su peor caso sobre todos los pesos de las aristas está a un factor constante de lo mejor que puede hacer cualquier algoritmo. Y en 2025, Ran Duan y sus coautores presentaron un algoritmo determinista para grafos dirigidos con pesos no negativos que se ejecuta en tiempo O(E log2/3 V) y supera el O(E + V log V) de Dijkstra en grafos dispersos. Escapa de lo que durante mucho tiempo se llamó la barrera de la ordenación porque, a diferencia de Dijkstra, no produce los vértices en orden de distancia. Cada uno de los tres artículos ganó un premio al mejor artículo. Ninguno de ellos ha sustituido todavía a los tres clásicos en el código de cada día, donde importan más la sencillez y las constantes pequeñas.

12. Errores comunes

ErrorPor qué está malSolución
Ejecutar Dijkstra en un grafo con aristas negativasLos vértices cerrados nunca se revisan; en el ejemplo, A, C y T salen malUsa Bellman-Ford, o Johnson para todos los pares
Sumar una constante para que los pesos sean no negativosPenaliza las rutas con más aristas y cambia cuál es la ruta más cortaRepesa con potenciales, como hace el algoritmo de Johnson
Ignorar los ciclos negativosLas distancias son menos infinito; cualquier número que se dé carece de sentidoEjecuta una ronda extra de Bellman-Ford, o comprueba la diagonal de Floyd-Warshall
Una arista negativa en un grafo no dirigidoRecorrerla de ida y vuelta es un ciclo negativoTrata el problema como de caminos simples más cortos, que necesita otros métodos
Usar Floyd-Warshall para un solo origenO(V3) de trabajo para una sola fila de la tablaUsa Dijkstra o Bellman-Ford
Usar Floyd-Warshall en un grafo disperso enormeLa tabla de V por V puede no caber en memoriaEjecuta Dijkstra o Johnson desde los orígenes que necesites
Usar INF + w con un entero grande fijoInfinito más un peso negativo parece una distancia real, o se desbordaOmite las relajaciones desde vértices que siguen en infinito
Ejecutar Bellman-Ford exactamente V rondas y fiarse del resultadoLa ronda V es la ronda de prueba; si hay cambios en ella, existe un ciclo negativoEjecuta V - 1 rondas y luego comprueba una vez más

13. Glosario

TérminoSignificado
Caminos más cortos desde un único origenDistancias desde un vértice a todos los demás vértices
Caminos más cortos entre todos los paresDistancias entre cada par ordenado de vértices
RelajaciónComprobar si una ruta que pasa por una arista más es más barata y, si lo es, anotarla
Vértice cerradoEn el algoritmo de Dijkstra, un vértice cuya distancia se ha declarado definitiva
Arista negativaUna arista con un peso menor que cero
Ciclo negativoUn ciclo cuyos pesos suman menos de cero
Cola de prioridadUna estructura que siempre devuelve la clave más pequeña; normalmente un montículo binario
PotencialUn número h(v) por vértice que se usa para repesar las aristas sin cambiar qué rutas son las más cortas
Grafo densoUn grafo en el que una gran parte de todos los pares de vértices están unidos por aristas
Grafo dispersoUn grafo con pocas aristas en comparación con V2, normalmente un pequeño múltiplo de V
Clausura transitivaLa tabla de qué vértices pueden alcanzar a cuáles

14. Preguntas frecuentes

¿Qué diferencia hay entre Dijkstra, Bellman-Ford y Floyd-Warshall?

Dijkstra y Bellman-Ford calculan las distancias desde un vértice origen a todos los demás vértices, mientras que Floyd-Warshall calcula la distancia entre cada par de vértices. Dijkstra es el más rápido, pero requiere pesos de arista no negativos. Bellman-Ford admite pesos negativos y detecta ciclos negativos en tiempo O(VE). Floyd-Warshall también admite pesos negativos y detecta ciclos negativos, en tiempo O(V al cubo) y con memoria O(V al cuadrado).

¿Por qué Dijkstra no funciona con pesos negativos?

Dijkstra declara definitiva la distancia de un vértice en cuanto es el vértice abierto más cercano, y nunca vuelve a revisarlo. Eso solo es seguro si ninguna arista posterior puede bajar el coste. Una arista negativa puede hacer exactamente eso, así que Dijkstra puede dar por definitivo un vértice demasiado pronto e informar de distancias demasiado altas, sin ningún aviso.

¿Es Bellman-Ford más lento que Dijkstra?

Sí, siempre que ambos sean aplicables. Bellman-Ford se ejecuta en tiempo O(VE), mientras que Dijkstra con un montículo binario se ejecuta en O((V + E) log V). En un grafo con 10.000 vértices y 50.000 aristas, eso supone hasta 500 millones de relajaciones de aristas frente a alrededor de un millón de pasos de montículo. Usa Bellman-Ford solo cuando los pesos puedan ser negativos o haya que detectar un ciclo negativo.

¿Cuándo debo usar Floyd-Warshall en lugar de ejecutar Dijkstra desde cada vértice?

Usa Floyd-Warshall cuando necesites las distancias entre todos los pares en un grafo pequeño, de hasta unos pocos cientos de vértices, o en un grafo denso en el que la mayoría de los pares de vértices están unidos por aristas, y también cuando los pesos puedan ser negativos. En grafos grandes y dispersos, ejecutar Dijkstra desde cada vértice, o el algoritmo de Johnson si los pesos pueden ser negativos, es mucho más rápido y usa mucha menos memoria.

¿Cómo se detecta un ciclo negativo?

Ejecuta Bellman-Ford durante V - 1 rondas y después una más: si la ronda extra todavía baja alguna distancia, existe un ciclo negativo, y seguir los punteros a los padres desde el vértice que ha cambiado lleva hasta él. Con Floyd-Warshall, existe un ciclo negativo exactamente cuando alguna entrada de la diagonal de la matriz de distancias final es negativa. Dijkstra no puede detectar ciclos negativos.

¿Qué es el algoritmo de Johnson?

El algoritmo de Johnson calcula los caminos más cortos entre todos los pares en grafos dispersos con aristas negativas. Ejecuta Bellman-Ford una vez para obtener un potencial h(v) para cada vértice, repesa cada arista como w(u, v) + h(u) - h(v), que nunca es negativo y conserva los mismos caminos más cortos, y después ejecuta Dijkstra desde cada vértice. Su tiempo de ejecución es O(VE + V(V + E) log V) con un montículo binario.

15. Referencias

Los algoritmos, las cotas y las atribuciones anteriores proceden de estas fuentes, listadas en orden cronológico.

  1. Shimbel, A. (1955). "Structure in Communication Nets." En Proceedings of the Symposium on Information Networks, 199 a 203. Brooklyn: Polytechnic Press of the Polytechnic Institute of Brooklyn.
  2. Ford, L. R. (1956). Network Flow Theory. Documento P-923. Santa Monica: RAND Corporation.
  3. Bellman, R. (1958). "On a Routing Problem." Quarterly of Applied Mathematics 16(1), 87 a 90.
  4. Dijkstra, E. W. (1959). "A Note on Two Problems in Connexion with Graphs." Numerische Mathematik 1, 269 a 271.
  5. Moore, E. F. (1959). "The Shortest Path Through a Maze." En Proceedings of an International Symposium on the Theory of Switching, 285 a 292. Cambridge, MA: Harvard University Press.
  6. Roy, B. (1959). "Transitivité et connexité." Comptes Rendus de l'Académie des Sciences 249, 216 a 218.
  7. Floyd, R. W. (1962). "Algorithm 97: Shortest Path." Communications of the ACM 5(6), 345.
  8. Warshall, S. (1962). "A Theorem on Boolean Matrices." Journal of the ACM 9(1), 11 a 12.
  9. Johnson, D. B. (1973). "A Note on Dijkstra's Shortest Path Algorithm." Journal of the ACM 20(3), 385 a 388.
  10. Johnson, D. B. (1977). "Efficient Algorithms for Shortest Paths in Sparse Networks." Journal of the ACM 24(1), 1 a 13.
  11. Fredman, M. L. y Tarjan, R. E. (1987). "Fibonacci Heaps and Their Uses in Improved Network Optimization Algorithms." Journal of the ACM 34(3), 596 a 615.
  12. Moy, J. (1998). "OSPF Version 2." RFC 2328. Internet Engineering Task Force.
  13. Malkin, G. (1998). "RIP Version 2." RFC 2453. Internet Engineering Task Force.
  14. Cormen, T. H., Leiserson, C. E., Rivest, R. L. y Stein, C. (2009). Introduction to Algorithms, 3.ª edición. Cambridge, MA: MIT Press.
  15. Frana, P. L. y Misa, T. J. (2010). "An Interview with Edsger W. Dijkstra." Communications of the ACM 53(8), 41 a 47.
  16. Hougardy, S. (2010). "The Floyd-Warshall Algorithm on Graphs with Negative Cycles." Information Processing Letters 110(8 a 9), 279 a 281.
  17. Sedgewick, R. y Wayne, K. (2011). Algorithms, 4.ª edición. Upper Saddle River, NJ: Addison-Wesley.
  18. Schrijver, A. (2012). "On the History of the Shortest Path Problem." Documenta Mathematica, Extra Volume ISMP, 155 a 167.
  19. Bernstein, A., Nanongkai, D. y Wulff-Nilsen, C. (2022). "Negative-Weight Single-Source Shortest Paths in Near-linear Time." Proceedings of the 63rd IEEE Symposium on Foundations of Computer Science, 600 a 611.
  20. Haeupler, B., Hladík, R., Rozhoň, V., Tarjan, R. E. y Tětek, J. (2024). "Universal Optimality of Dijkstra via Beyond-Worst-Case Heaps." Proceedings of the 65th IEEE Symposium on Foundations of Computer Science, 2099 a 2130.
  21. Duan, R., Mao, J., Mao, X., Shu, X. y Yin, L. (2025). "Breaking the Sorting Barrier for Directed Single-Source Shortest Paths." Proceedings of the 57th ACM Symposium on Theory of Computing, 36 a 44.

Observa cómo actúa la arista negativa

Construye el ejemplo en el visualizador, pon la arista de B a A en -4 y ejecuta Bellman-Ford desde S. Observa cómo el reembolso llega a A y después pasa a C y T.

Abrir el visualizador

Compara los tres algoritmos

Ejecuta Dijkstra paso a paso sobre tu propio grafo y observa qué vértice cierra a continuación. Después haz negativo un peso y compáralo con Bellman-Ford.

Abrir el visualizador de Dijkstra