28. Algoritmo de Bellman-Ford

Bellman-Ford calcula caminos mínimos desde un origen incluso cuando existen pesos negativos. Además puede detectar ciclos negativos alcanzables que impiden definir ciertas distancias mínimas.

28.1 Introducción

Dijkstra es eficiente, pero su estrategia voraz deja de ser segura cuando una arista tiene peso negativo. Bellman-Ford elimina esa restricción mediante una idea diferente: relajar sistemáticamente todas las aristas varias veces.

El algoritmo obtiene las distancias mínimas desde un origen, reconstruye rutas mediante predecesores y reconoce ciclos negativos que sean alcanzables desde ese origen.

28.2 Cuándo se utiliza

Bellman-Ford es apropiado cuando:

  • el grafo puede contener aristas con pesos negativos;
  • necesitamos detectar ciclos de costo negativo;
  • el problema pide caminos mínimos desde un único origen;
  • preferimos una implementación directa basada en una lista de aristas.

Si todos los pesos son no negativos, Dijkstra suele ser más rápido.

28.3 Inicialización

Como en otros algoritmos de caminos mínimos, se comienza con:

distancia[origen] = 0
distancia[resto] = ∞
padre[todos] = null

El infinito significa que todavía no se conoce ningún camino desde el origen hasta ese vértice.

28.4 Relajación de aristas

Para cada arista u → v de peso w se evalúa:

Si distancia[u] ≠ ∞ y distancia[u] + w < distancia[v]:
distancia[v] = distancia[u] + w
padre[v] = u

La comprobación de infinito evita realizar operaciones desde un vértice que todavía no es alcanzable.

28.5 Por qué se necesitan V − 1 rondas

Un camino mínimo simple en un grafo con V vértices utiliza como máximo V − 1 aristas. Si repitiera un vértice contendría un ciclo; cuando no hay ciclos negativos relevantes, ese ciclo puede eliminarse sin empeorar el camino.

Después de la ronda i, Bellman-Ford garantiza las mejores distancias para caminos de hasta i aristas, independientemente del orden en que estén almacenadas.

V vértices ⇒ como máximo V − 1 aristas en un camino mínimo simple.

28.6 Simulación interactiva paso a paso

Avanza arista por arista para observar las relajaciones. Puedes activar D → A con peso −4; junto con A → C y C → D forma un ciclo de costo negativo.

Distancias y padres

Acción actual

Pulsa Iniciar.
Grafo sin ciclo negativo.
Ronda0 / 4
Arista examinada
Mejoras0
Ciclo negativoNo detectado

Rosa señala la arista examinada y el vértice cuya distancia acaba de mejorar. Las aristas celestes representan los predecesores actuales.

28.7 Detección de ciclos negativos

Después de las V − 1 rondas se examinan nuevamente todas las aristas. Si alguna distancia todavía puede disminuir, existe un ciclo negativo alcanzable desde el origen.

Mejora durante la ronda V ⇒ ciclo negativo alcanzable.

La palabra alcanzable es importante: un ciclo negativo en una componente a la que el origen no puede llegar no afecta sus caminos mínimos.

28.8 Algoritmo completo en JavaScript

function bellmanFord(vertices, aristas, origen) {
  const distancia = Object.fromEntries(vertices.map(v => [v, Infinity]));
  const padre = Object.fromEntries(vertices.map(v => [v, null]));
  distancia[origen] = 0;

  for (let ronda = 1; ronda < vertices.length; ronda++) {
    let cambio = false;

    for (const { origen: u, destino: v, peso } of aristas) {
      if (distancia[u] !== Infinity &&
          distancia[u] + peso < distancia[v]) {
        distancia[v] = distancia[u] + peso;
        padre[v] = u;
        cambio = true;
      }
    }
    if (!cambio) break;
  }

  for (const { origen: u, destino: v, peso } of aristas) {
    if (distancia[u] !== Infinity &&
        distancia[u] + peso < distancia[v]) {
      return { distancia, padre, cicloNegativo: true };
    }
  }
  return { distancia, padre, cicloNegativo: false };
}

28.9 Finalización anticipada

Si una ronda completa no produce ninguna mejora, las distancias ya son estables. Las rondas restantes no podrían cambiar nada y el algoritmo puede detenerse.

cero relajaciones exitosas en una ronda ⇒ finalizar anticipadamente

Esta optimización mejora muchos casos prácticos, aunque la complejidad del peor caso continúa siendo O(VE).

28.10 El orden de las aristas

El resultado final no depende del orden de las aristas, pero la velocidad con la que se propagan las mejoras sí. Una ronda puede transmitir varias actualizaciones si las aristas aparecen en un orden favorable.

La garantía de V − 1 rondas cubre cualquier orden posible. Por eso no debemos asumir que una sola pasada basta solo porque funcionó en un ejemplo particular.

28.11 Reconstrucción de caminos

Cuando una relajación mejora a v, se guarda u como su padre. Si el destino tiene una distancia mínima finita y no está afectado por un ciclo negativo, la ruta se reconstruye siguiendo padres hacia el origen.

function reconstruir(padre, origen, destino) {
  const camino = [];
  let actual = destino;

  while (actual !== null) {
    camino.push(actual);
    if (actual === origen) return camino.reverse();
    actual = padre[actual];
  }
  return null;
}

28.12 Reconstruir un ciclo negativo

Si una relajación adicional mejora al vértice x, podemos seguir su cadena de padres V veces para asegurar que ingresamos al ciclo. Desde allí continuamos hasta repetir un vértice.

x = padre[x], repetido V veces → x está dentro del ciclo.

Mostrar el ciclo resulta útil para diagnosticar reglas de conversión, dependencias o transacciones inconsistentes.

28.13 Vértices afectados por el ciclo

No todos los vértices del grafo tienen necesariamente distancia −∞. Quedan afectados los nodos que pertenecen a un ciclo negativo alcanzable o que pueden alcanzarse desde él.

Después de detectar los vértices mejorables en la ronda adicional, una búsqueda desde ellos permite marcar todos los destinos afectados.

28.14 Bellman-Ford y Dijkstra

CaracterísticaBellman-FordDijkstra
Pesos negativosPermitidosNo permitidos
Ciclos negativosLos detectaNo los maneja
EstrategiaRelajar todas las aristas por rondasFijar el mínimo pendiente
Complejidad habitualO(VE)O((V + E) log V)

28.15 Complejidad

Se realizan como máximo V − 1 rondas principales y cada una examina E aristas:

Tiempo: O(VE)
Espacio adicional: O(V)

La ronda de detección agrega O(E), que no modifica la complejidad asintótica.

28.16 Aplicaciones

  • Protocolos de enrutamiento por vector de distancias.
  • Mercados y conversiones representadas mediante costos.
  • Problemas con bonificaciones o ganancias modeladas como pesos negativos.
  • Detección de restricciones inconsistentes.
  • Cálculo de caminos cuando no puede garantizarse que todos los costos sean positivos.

28.17 Errores comunes

  • Ejecutar solo una ronda de relajaciones.
  • Olvidar comprobar que el origen de la arista tenga distancia finita.
  • Confundir una arista negativa con un ciclo negativo.
  • Detectar ciclos negativos que no son alcanzables desde el origen.
  • No realizar la ronda adicional de comprobación.
  • Reconstruir una ruta finita hacia un vértice afectado por un ciclo negativo.
  • Suponer que el orden de las aristas no afecta la cantidad de rondas necesarias.

28.18 Qué debes recordar de este tema

  • Bellman-Ford admite pesos negativos.
  • Relaja todas las aristas hasta V − 1 veces.
  • Una ronda sin mejoras permite finalizar antes.
  • Una mejora en la ronda adicional revela un ciclo negativo alcanzable.
  • Los padres permiten reconstruir rutas y diagnosticar ciclos.
  • Su tiempo es O(VE) y su espacio adicional O(V).

28.19 Conclusión

Bellman-Ford sacrifica velocidad para trabajar en un escenario más general que Dijkstra. Sus rondas garantizan la propagación de mejoras y su verificación final distingue los caminos mínimos válidos de situaciones dominadas por ciclos negativos.

En el próximo tema estudiaremos Floyd-Warshall, que calcula caminos mínimos entre todos los pares de vértices mediante programación dinámica.