Alex's Anthology of Algorithms Common Code for Contests in Concise C++
Graphs / Shortest Paths

4.3.4 Shortest Path (Bellman-Ford)

4-Graphs/4.3.4_Shortest_Path_(Bellman-Ford).cpp

Given a starting node in a weighted, directed graph with possibly negative weights, visit every reachable node and determine the minimum distance to each such node. Optionally, reconstruct the shortest path to a destination node using the shortest-path tree from the predecessor array pred.

Bellman-Ford relaxes every edge in the graph up to $n - 1$ times, stopping early if a pass makes no changes. Since any shortest path uses at most $n - 1$ edges, all distances are correct after these passes unless a negative-weight cycle keeps reducing them, which a further pass detects. Bellman-Ford will also detect whether the graph contains a negative-weight cycle reachable from the start node, in which case the affected shortest paths are undefined. (To detect a negative cycle anywhere in the graph, add a virtual source with zero-weight edges to every node and start from it.)

  • bellman_ford(n, start) populates dist and pred for a global, pre-populated edge list edges whose endpoints must be numbered $[0, {\htmlClass{math-inline-code}{\texttt{n}}})$, and returns whether no reachable negative-weight cycle was found.
  • get_path(dest) returns the path from start to dest, or an empty vector if dest is unreachable, provided the most recent call to bellman_ford() returned true. If it returned false, a reachable negative-weight cycle leaves the distances and paths undefined.

For path reconstruction, pred[v] stores the node immediately before v on the shortest path from start to v, or $-1$ if v is start or unreachable. Follow pred backward from the destination to start, then reverse that sequence to recover the path.

Implementation

#include <algorithm>
#include <cstdint>
#include <tuple>
#include <vector>

const int64_t INF = INT64_MAX / 4;
std::vector<std::tuple<int, int, int>> edges;  // (u, v, w)
std::vector<int64_t> dist;
std::vector<int> pred;

bool bellman_ford(int n, int start) {
  dist.assign(n, INF);
  pred.assign(n, -1);
  dist[start] = 0;
  for (int i = 0; i < n - 1; i++) {
    bool changed = false;
    for (auto [u, v, w] : edges) {
      // The dist[u] != INF guard avoids relaxing out of unreachable nodes: a negative edge from an
      // unreachable u would otherwise give v a bogus finite distance (INF + w < INF).
      if (dist[u] != INF && dist[v] > dist[u] + w) {  // Overflow warning.
        dist[v] = dist[u] + w;
        pred[v] = u;
        changed = true;
      }
    }
    if (!changed) break;
  }
  // Check for a negative-weight cycle reachable from the start node.
  for (auto [u, v, w] : edges) {
    if (dist[u] != INF && dist[v] > dist[u] + w) {  // Overflow warning.
      return false;
    }
  }
  return true;
}

std::vector<int> get_path(int dest) {
  if (dist[dest] == INF) {
    return {};
  }
  std::vector<int> path;
  for (int v = dest; v != -1; v = pred[v]) {
    path.push_back(v);
  }
  std::reverse(path.begin(), path.end());
  return path;
}

Example Usage

#include <cassert>
using namespace std;

int main() {
  //    w=1      w=2
  // 0 -----> 1 -----> 2
  // |                 ^
  // +-----------------+
  //         w=5
  edges.emplace_back(0, 1, 1);
  edges.emplace_back(1, 2, 2);
  edges.emplace_back(0, 2, 5);
  assert(bellman_ford(3, 0));
  assert((dist == vector<int64_t>{0, 1, 3}));
  assert((pred == vector<int>{-1, 0, 1}));
  assert((get_path(2) == vector<int>{0, 1, 2}));

  edges = {{0, 1, -1}, {1, 0, -1}};
  assert(!bellman_ford(2, 0));
  return 0;
}