BFS visits vertices in order of distance (in edges) and gives unweighted shortest paths; DFS gives a tree with timestamps whose structure (back edges, subtree intervals, lowlinks) drives cycles, topological order, bridges, SCCs and Euler tours. Both are .
BFS
vector<int> bfs(const vector<vector<int>>& g, const vector<int>& sources) { // multi-source
vector<int> d(sz(g), -1); queue<int> q;
for (int s : sources) if (d[s] < 0) d[s] = 0, q.push(s);
while (sz(q)) { int u = q.front(); q.pop(); for (int v : g[u]) if (d[v] < 0) d[v] = d[u] + 1, q.push(v); }
return d;
}Mark when pushing, not when popping, so each vertex enters the queue once. Keep par[v] = u to recover paths.
Proof: BFS distances are shortest
Invariant: the queue holds vertices with distances (nondecreasing, spread ), and every vertex at true distance has already been popped. When (distance ) is popped, its unvisited neighbours get , keeping the invariant. By induction on the true distance : a vertex at distance has a neighbour on a shortest path at distance , which is popped with label ; at that moment is labelled unless it was already labelled, and any earlier label is (labels are nondecreasing in pop order) and (labels are lengths of real paths). Multi-source BFS is the same on a graph with a super-source joined to all sources.
Variants:
- Grids: direction arrays, cell id ; mark walls/visited in place.
- Multi-source: “distance to the nearest X”; fire spreading. For “escape before the monsters”: BFS from all monsters first, then BFS for the player using only cells reached strictly earlier than the monsters (CSES Monsters).
- State BFS: vertices , , .
- Bidirectional BFS: expand the smaller frontier; instead of on puzzle state spaces. Stop when a full layer has been expanded and the frontiers meet; take the minimum over meeting edges.
- Weights 0/1 or small integers: 0-1 BFS, Dial’s buckets (Shortest Paths).
- Lexicographically smallest shortest path: BFS from the target for distances, then walk from the source choosing the smallest label among neighbours with distance one less, processing whole layers at once (keep the set of current best vertices).
BFS on the complement graph
The complement of a sparse graph is dense, but BFS on it costs : keep the unvisited vertices in a list; when expanding , every unvisited vertex not adjacent to (in the sparse graph) is visited and removed for good, and every vertex that stays is paid for by an edge of the sparse graph.
vector<int> complementBFS(const vector<vector<int>>& g, int s) { // g = edges NOT present
int n = sz(g); vector<int> d(n, -1), mark(n, -1), rest; queue<int> q;
for (int v = 0; v < n; v++) if (v != s) rest.push_back(v);
d[s] = 0; q.push(s);
while (sz(q)) {
int u = q.front(); q.pop();
for (int v : g[u]) mark[v] = u;
vector<int> keep;
for (int v : rest) if (mark[v] == u) keep.push_back(v); else d[v] = d[u] + 1, q.push(v);
rest.swap(keep);
}
return d;
}Components of the complement (CF 920E) and MST of a complete graph with 0/1 weights (CF 1242B) use the same loop.
Proof:
When is expanded, each vertex scanned in
restis either removed (at most times overall) or kept, and it is kept only if is an edge of the sparse graph. Each vertex is expanded once, so each sparse edge causes at most two “keep” events. Total .
Knight distance on an unbounded board
ll knightDist(ll x, ll y) { // offset (x, y), unbounded board
x = llabs(x), y = llabs(y); if (x < y) swap(x, y);
if (x == 1 && y == 0) return 3;
if (x == 2 && y == 2) return 4;
ll k = max((x + 1) / 2, (x + y + 2) / 3);
if ((k - x - y) % 2) k++;
return k;
}On a bounded board, corners change small distances (corner to its diagonal neighbour needs 4), so BFS there.
Proof sketch: the knight formula
Lower bounds: a move changes by at most 2 and by at most 3, so and ; each move changes by or (an odd number), so . Upper bound: for use moves , which reach with any of the right parity up to ; for mix and moves to reach in steps up to a parity fix of one extra move. The construction only fails for tiny offsets, where the knight must step away from the target first; checking all offsets with (by hand or BFS) leaves exactly the exceptions and .
DFS and its timestamps
int timer = 0; vector<int> tin, tout, par;
void dfs(int u, int p, const vector<vector<int>>& g) { // recursion depth = path length: beware 1e6
tin[u] = timer++; par[u] = p;
for (int v : g[u]) if (tin[v] < 0) dfs(v, u, g);
tout[u] = timer++;
}Iterative DFS that matches recursive order keeps (vertex, next edge index) on an explicit stack.
| Edge found when scanning | Directed graph | Undirected graph |
|---|---|---|
| unvisited | tree edge | tree edge |
| on the current path (grey) | back edge: cycle | back edge: cycle (skip the parent edge by edge id, not vertex, for multigraphs) |
| finished, | forward edge | impossible |
| finished, | cross edge | impossible |
- is an ancestor of iff and ; subtrees are contiguous ranges of .
- Topological order = decreasing (Topological Sort).
- Lowlinks give bridges, articulation points and SCCs (Bridges, SCC).
- By itself DFS answers almost nothing; its value is as the skeleton underneath a dozen other algorithms: cycle detection, bipartiteness, topological sort, bridges/articulation points, SCCs, Euler tours, augmenting paths in flows, and maze generation.
Proof: parenthesis and white-path theorems, and the edge classification
Parenthesis: the intervals of any two vertices are nested or disjoint, because a call that starts during another call finishes before it (call stack discipline); nested exactly when one is a DFS-tree ancestor of the other.
White path: becomes a descendant of iff at time there is a path from to of unvisited (white) vertices. If such a path existed but were not a descendant, take the first vertex on the path that isn’t a descendant; its predecessor is a descendant and scans the edge to before finishing, while is still white (it can only be discovered inside ‘s call), so would become a descendant, contradiction.
Undirected graphs have no cross/forward edges: when scans edge with already finished, must have scanned earlier while was white or grey; if was white, would have become ‘s descendant, so was grey and the edge is a back edge from ‘s side. Cycle iff back edge: a back edge closes a cycle with the tree path; conversely in a cycle, the first discovered vertex reaches all others by white paths, so the cycle’s last vertex before is a descendant of with an edge to : a back edge.
Connected components and flood fill
DFS/BFS from each unvisited vertex, or DSU when edges arrive online. In directed graphs this gives weakly connected components; for reachability use SCCs. Number of edges to add to connect everything: . Grid flood fill is the same on the implicit grid graph (label regions, count islands, area of each region — CSES Counting Rooms, Labyrinth, Monsters).
| Situation | Use |
|---|---|
| static graph, adjacency list ready | DFS/BFS |
| edges arrive one at a time | DSU |
| edges are also removed | dynamic connectivity |
| directed graph | SCC — plain components are the wrong notion |
| need bridge-resilience | 2-edge-connected components |
| need cut-vertex-resilience | biconnected components |