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.
| Graph | Algorithm | Time |
|---|---|---|
| unweighted | BFS | |
| weights in | 0-1 BFS (deque) | |
| small integer weights | Dial’s buckets (circular, buckets) | |
| DAG, any weights | relax in topological order | |
| nonnegative weights | Dijkstra with binary heap / dense array | / |
| negative weights | Bellman–Ford (SPFA is hackable) | |
| all pairs, | Floyd–Warshall | |
| all pairs, sparse, negative edges | Johnson |
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.
Proof: a popped vertex has its final distance
Let be the set of popped (finalised) vertices and suppose is the first vertex popped with (true distance). Take a shortest path and let be its first vertex outside , with predecessor . When was popped, and the edge was relaxed, so , using nonnegative weights for the last inequality. Hence , so would be popped before (and since ), contradicting the choice of . Each edge is relaxed once and pushes at most one heap entry: .
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.
Proof: the deque stays sorted with at most two values
Invariant: the deque’s labels are nondecreasing from front to back and span at most where is the front’s label. Popping (label ), a 0-edge gives label , pushed to the front, and a 1-edge gives , pushed to the back; both preserve the invariant. Hence vertices are popped in nondecreasing order of label, exactly as in Dijkstra, so the Dijkstra proof applies; a vertex may be pushed again only when its label strictly decreases, which happens at most once per incoming edge.
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.
Proof: Bellman–Ford, cycle extraction
After round , the length of the shortest walk from to with at most edges (induction: the last edge of such a walk is relaxed in round after its prefix is correct). Without negative cycles, shortest walks can be taken simple, with at most edges, so rounds suffice and no edge can be relaxed afterwards. Conversely if a negative cycle is reachable and no edge in were relaxable, summing around gives : contradiction. So a reachable negative cycle always leaves a relaxable edge, and propagates to everything it reaches within more rounds.
Extraction: if round still relaxes , the parent pointers from form a walk backwards; following them times must enter a cycle of the parent graph. Every cycle in the parent graph has negative weight: when the last edge of such a cycle was set, all the parent edges satisfied with strict inequality for the edge just relaxed, and summing around the cycle gives .
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).
Proof: the -th round allows intermediates
Invariant: after processing , is the shortest path from to whose intermediate vertices lie in (assuming no negative cycles). A shortest such path either avoids (already counted) or passes through once, splitting into and with intermediates in , both correct by induction. Updating in place is safe because and don’t change during round (that would need ). With negative cycles, exactly for vertices on one, and through such a is unbounded; clamping at prevents the exponential decrease from overflowing.
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).
Proof: reduced weights
Telescoping: for any path from to . If , the triangle inequality gives .
Variants
| Variant | Approach |
|---|---|
| number of shortest paths | carry 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 paths | in 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 walks | pop each vertex up to times (), or Eppstein (K Shortest) |
| one edge may be free / halved | state |
| 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 shortest | compare pairs |
| time-dependent edges (FIFO: leaving later never arrives earlier) | Dijkstra with arrival time |
| multiple sources / targets | virtual 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.