All single-source algorithms repeat one operation, relaxation (), and differ in the order: by distance (Dijkstra), by layers of a deque (0-1 BFS), by topological order (DAG), or all edges times (Bellman–Ford). All-pairs: Floyd–Warshall , or Johnson’s reweighting + Dijkstras.

GraphAlgorithmTime
unweightedBFS
weights in 0-1 BFS (deque)
small integer weights Dial’s buckets (circular, buckets)
DAG, any weightsrelax in topological order
nonnegative weightsDijkstra with binary heap / dense array /
negative weightsBellman–Ford (SPFA is hackable)
all pairs, Floyd–Warshall
all pairs, sparse, negative edgesJohnson

Dijkstra

const ll INF = LLONG_MAX / 4;
vector<ll> dijkstra(const vector<vector<pair<int, ll>>>& g, int s, vector<int>* par = nullptr) {
    vector<ll> d(sz(g), INF); if (par) par->assign(sz(g), -1);
    priority_queue<pair<ll, int>, vector<pair<ll, int>>, greater<>> pq; d[s] = 0; pq.push({0, s});
    while (sz(pq)) {
        auto [du, u] = pq.top(); pq.pop();
        if (du != d[u]) continue;                           // stale entry
        for (auto [v, w] : g[u]) if (du + w < d[v]) { d[v] = du + w; if (par) (*par)[v] = u; pq.push({d[v], v}); }
    }
    return d;
}

Dense graphs (): pick the unvisited vertex with minimum by a linear scan, total. Never run Dijkstra with negative edges: it silently returns wrong answers. The union of one shortest path to each vertex (with consistent tie-breaking) forms a shortest-path tree rooted at the source — the structure that makes “which edges can appear on some/every shortest path” questions tree questions.

0-1 BFS and Dial

vector<int> bfs01(const vector<vector<pair<int, int>>>& g, int s) {   // weights 0 or 1
    vector<int> d(sz(g), INT_MAX); deque<int> dq; d[s] = 0; dq.push_back(s);
    while (sz(dq)) {
        int u = dq.front(); dq.pop_front();
        for (auto [v, w] : g[u]) if (d[u] + w < d[v]) { d[v] = d[u] + w; w ? dq.push_back(v) : dq.push_front(v); }
    }
    return d;
}

Uses: grids where some moves are free (break a wall = 1), minimum edge reversals (reverse edge costs 1), minimum direction changes (state = cell × direction), portals/teleports (0 to teleport, 1 for a normal step), minimum characters to insert (0 to match, 1 to insert), connecting cells where some links are free. For weights in use circular buckets (Dial), — worth it only when , since beyond that the bucket sweep costs more than Dijkstra’s heap; a radix heap (bucket by the highest differing bit rather than the exact value) gives for integer weights, and is the general version of which Dial and 0-1 BFS’s deque are the small- special cases.

Bellman–Ford and negative cycles

vector<ll> bellmanFord(int n, const vector<array<ll, 3>>& E, int s) {   // -INF: arbitrarily small (via a negative cycle)
    vector<ll> d(n, INF); d[s] = 0;
    for (int it = 0; it + 1 < n; it++) for (auto [u, v, w] : E) if (d[u] < INF && d[u] + w < d[v]) d[v] = d[u] + w;
    for (int it = 0; it < n; it++) for (auto [u, v, w] : E) if (d[u] < INF && (d[u] == -INF || d[u] + w < d[v])) d[v] = -INF;
    return d;
}
vector<int> negativeCycle(int n, const vector<array<ll, 3>>& E) {        // any negative cycle (in order), or empty
    vector<ll> d(n, 0); vector<int> par(n, -1); int x = -1;                // d = 0 everywhere: virtual source
    for (int it = 0; it < n; it++) { x = -1; for (auto [u, v, w] : E) if (d[u] + w < d[v]) d[v] = d[u] + w, par[v] = u, x = v; }
    if (x < 0) return {};
    for (int i = 0; i < n; i++) x = par[x];                                // step back n times: now on the cycle
    vector<int> cyc{x}; for (int v = par[x]; v != x; v = par[v]) cyc.push_back(v);
    reverse(all(cyc)); return cyc;
}

“Is the answer at unbounded” (CSES High Score): is iff some vertex on a negative cycle is reachable from and reaches ; the second loop above propagates that. Difference constraints reduce directly to this: build edge of weight , add a super-source at weight 0 to everything, and a negative cycle means the system is infeasible (Graph Basics).

SPFA (queue of vertices whose distance changed, skip re-queueing one already waiting) is fast on random graphs but on adversarial ones — these are well-known and easy to generate, so don’t submit plain SPFA where would TLE. Detect a negative cycle by counting enqueues: if any vertex is enqueued more than times, one is reachable from . Two heuristics narrow the gap to Dijkstra without fixing the worst case: SLF (small label first — push to the front of the queue instead of the back when is below the current front’s label, a generalisation of 0-1 BFS’s deque trick) and LLL (large label last — keep rotating the front to the back while its label exceeds the queue’s average); both together is the common “optimised SPFA,” and both remain hackable.

Floyd–Warshall

void floyd(vector<vector<ll>>& d) {                       // d[i][i] = 0, INF if no edge; -INF marks unbounded pairs
    int n = sz(d);
    for (int k = 0; k < n; k++) for (int i = 0; i < n; i++) if (d[i][k] < INF) for (int j = 0; j < n; j++)
        if (d[k][j] < INF) d[i][j] = min(d[i][j], max(d[i][k] + d[k][j], -INF));
    for (int k = 0; k < n; k++) if (d[k][k] < 0) for (int i = 0; i < n; i++) for (int j = 0; j < n; j++)
        if (d[i][k] < INF && d[k][j] < INF) d[i][j] = -INF;
}

Path reconstruction: keep nxt[i][j] (first step), updated as nxt[i][j] = nxt[i][k]. Variants: transitive closure (, with bitsets ), bottleneck paths ( of ), counting shortest paths, adding vertices one by one in any order (“answer queries after activating vertex ” is exactly the outer loop).

Potentials and Johnson

For any potential , the reduced weights change every path by the same constant , so shortest paths are preserved. With = Bellman–Ford distances from a virtual source, every (triangle inequality), and Dijkstra works. Johnson: one Bellman–Ford + Dijkstras, . The same trick keeps min-cost flow on Dijkstra (Min-Cost Flow).

Variants

VariantApproach
number of shortest pathscarry counts: reset on strict improvement, add on ties (process in Dijkstra pop order)
shortest-path DAG / edges on some shortest path using a forward and a backward run
edges on all shortest pathsin the shortest-path DAG, edges that are bridges between distance layers: count paths from both ends, (use two moduli)
second shortest walk (may repeat vertices)keep the two best distinct distances per vertex; a vertex is popped at most twice
shortest walkspop each vertex up to times (), or Eppstein (K Shortest)
one edge may be free / halvedstate
bottleneck (minimise the max edge)Dijkstra with instead of , or the MST path maximum — turning bottleneck queries into path-max-on-a-tree queries, answerable in each via binary lifting
fewest edges among shortestcompare pairs
time-dependent edges (FIFO: leaving later never arrives earlier)Dijkstra with arrival time
multiple sources / targetsvirtual super-source, or seed the heap with all sources
distances from all vertices to Dijkstra on the reversed graph

Hidden DAGs

A graph with cycles can still have a DAG hiding inside it:

  • Condensation. Contracting the SCCs of any directed graph gives a DAG; longest path there, weighted by component size, answers “largest set of mutually reachable vertices along a path.”
  • Layered state. If the state includes a quantity that only increases (steps taken, fuel spent), the state graph is a DAG even when the underlying graph has cycles.
  • Monotone grids. A grid where moves are only right/down is a DAG regardless of how the costs look.

Every DP is a DAG shortest path

A DP’s states and transitions form a DAG — “compute in dependency order” is exactly a topological order, and the recurrence is relaxation. This is why a DP with a cyclic dependency needs Dijkstra or Bellman–Ford instead of a plain loop, and it is also the reason LCS, edit distance, and grid-path counting are “DAG DPs in disguise”: the grid or the two-string product is the DAG. See DP on DAGs.