⚙️AlgorithmIntermediate

Multi-Source BFS

Key Points

  • Multi-source BFS explores an unweighted graph starting from several sources at once to compute the minimum distance to any source for every vertex.
  • Initialize the queue with all source vertices at distance 0 and perform a regular BFS that expands level by level.
  • The distance assigned to a vertex is the length of the shortest path from that vertex to the nearest source.
  • This technique models simultaneous spreading processes like fire, infection, or signal propagation from multiple points.
  • It is optimal for unweighted graphs because BFS guarantees shortest paths in terms of edge count.
  • Use a visited or distance array to avoid revisiting nodes and to store the best-known distance.
  • The time complexity is O(n + m) for a graph with n vertices and m edges, and space is O(n + m).
  • Applications include nearest facility queries, preprocessing for layered algorithms, and grid problems with obstacles.

Prerequisites

  • Graphs and adjacency listsYou must know how to represent and iterate neighbors efficiently.
  • Single-source BFSMulti-source BFS extends BFS; understanding the single-source version is essential.
  • Queues and FIFO behaviorBFS correctness relies on processing vertices in first-in-first-out order.
  • Grid traversal and boundary checkingMany practical problems occur on 2D grids with obstacles and require careful index handling.
  • Shortest paths in unweighted graphsUnderstanding why BFS finds minimal edge-count paths clarifies why multi-source BFS is optimal.
  • Dijkstra and 0-1 BFS (awareness)To avoid misuse of BFS on weighted graphs, you should know when to switch algorithms.

Detailed Explanation

Tap terms for definitions

01Overview

Multi-source BFS (Breadth-First Search) is a variation of the classic BFS that begins simultaneously from multiple starting points (sources) instead of just one. In an unweighted graph, standard BFS finds the shortest path (fewest edges) from a single source to all other vertices. Multi-source BFS extends this idea to compute, for each vertex v, the distance to the closest source among a set S of sources. The algorithm is simple: place all sources into the initial queue with distance 0 and expand the search as usual level by level. Because all sources start at the same depth, the first time we visit a vertex we know we have found its minimum distance to the set S. This is extremely useful when the problem naturally describes a simultaneous process—like fire breaking out in several locations, Wi-Fi coming from multiple routers, or finding the closest hospital from any house. The algorithm remains linear in the size of the graph, preserves the simplicity and determinism of BFS, and is easy to implement using a queue, a distance array initialized to infinity (or -1), and a loop that relaxes neighbors by one unit. It is a common building block in competitive programming and graph processing pipelines, especially on grids and road networks where edge weights are uniform or absent.

02Intuition & Analogies

Imagine a city map with several ice-cream trucks (the sources) that all start ringing their bells at the same moment. Sound waves spread out one street per minute. If you stand at any corner, how many minutes until you hear the closest truck? Instead of doing this calculation for each truck separately and taking the minimum, you can simulate all trucks ringing together. The earliest time the sound reaches your corner is the answer—and that is exactly what multi-source BFS computes. Think of the BFS queue as a crowd of ripples moving outward in concentric layers. With one source, there is a single ripple; with many sources, there are multiple ripples expanding simultaneously. When two ripples meet at an intersection, the earliest arriving ripple determines the shortest time to that point. Because all ripples advance one edge per step, the moment a vertex is first touched is guaranteed to be optimal. In a grid, this looks like multiple circles (actually Manhattan diamonds) growing and eventually tiling the board; obstacles block the ripples, making them go around. In facility location terms, each home is automatically assigned to its nearest facility by the very act of the wavefronts propagating, no explicit comparisons needed. The power of multi-source BFS is precisely this parallelism: by starting from all sources at once, you eliminate redundant work and obtain the minimum distance to any source in a single pass.

03Formal Definition

Let G = (V, E) be a finite, unweighted (or uniformly weighted) graph, and let S V be a set of source vertices. Define the graph distance d(u, v) as the length (number of edges) of the shortest path from u to v, or + if no such path exists. The multi-source distance from a vertex v to S is d(v, S) = d(v, s). Multi-source BFS computes the function D: V \{+\} where D(v) = d(v, S). The algorithm initializes a queue Q with all s S, setting D(s) = 0, and marks them as visited. While Q is not empty, it extracts a vertex u, and for each neighbor w of u such that D(w) is unset (or +), it sets D(w) = D(u) + 1 and enqueues w. Since BFS processes vertices in nondecreasing distance order (by levels), the first time a vertex v is assigned a value, that value equals the minimal number of edges required to reach v from any source in S. If the graph is directed, edges follow their direction; if it is undirected, edges are symmetric. The algorithm runs in time O( + ) and space O( + ) using adjacency lists.

04When to Use

Use multi-source BFS when you need the shortest number of steps from every vertex to the nearest element of a set, in an unweighted setting. Typical cases include: (1) Facility proximity: given multiple hospitals, find for each home the distance to the nearest hospital. (2) Spreading processes: model fire, infection, or signals starting from several initial sites and determine the time each location is affected. (3) Grid problems: compute the minimal steps from any of several entrances/exits or hazards on a 2D board with walls. (4) Preprocessing layers: build a potential function D(v) = d(v, S) to guide later decisions (e.g., pruning searches or proving safety margins). (5) Assignments by nearest center: partition the graph into Voronoi regions where each vertex is assigned to the closest source (ties can be broken arbitrarily or by source ID). Avoid multi-source BFS if edges have varying nonnegative weights; in that case, Dijkstra’s algorithm from a virtual super-source connected to all sources is required. Also, if you only need distances from one source, single-source BFS suffices. When the graph is extremely large but sparse and you only care about a small subset of targets, consider bidirectional BFS from each target instead of full propagation.

⚠️Common Mistakes

Common pitfalls include: (1) Forgetting to enqueue all sources with distance 0 at the start, which ruins correctness because later-discovered sources no longer compete fairly for earliest arrival. (2) Marking visited too late: you should assign distance/visited status as soon as you enqueue a node to avoid pushing the same vertex multiple times. (3) Mixing 0-based and 1-based vertex indices, leaving some vertices unreachable due to off-by-one errors. (4) Reusing arrays without reinitialization; distances must be reset to INF (or -1) when running multiple test cases. (5) On grids, failing to check boundaries or obstacles, which causes out-of-range access or walking through walls. (6) Assuming multi-source BFS works with weighted edges; with weights, BFS can give wrong answers—use Dijkstra or 0-1 BFS for weights in {0,1}. (7) Not handling disconnected components; unreachable vertices should keep distance = INF (or -1), not garbage values. (8) Overlooking tie-breaking if you need to know which source is nearest; store a source_id along with the parent to reconstruct both the path and the assigned source deterministically. By addressing these issues—especially correct initialization, early marking, and appropriate algorithm choice—you preserve BFS’s guarantees and avoid subtle bugs.

Key Formulas

Distance to a Set

Explanation: The distance from vertex v to the source set S is the minimum distance from v to any single source s in S. Multi-source BFS computes this value for every vertex in one pass.

Unweighted Shortest Path

Explanation: In an unweighted graph, the distance between u and v is the length (number of edges) of the shortest path among all possible paths between them. BFS realizes this minimum by expanding in layers.

Handshake Lemma

Explanation: The sum of degrees equals twice the number of edges in an undirected graph. This justifies that BFS’s total neighbor scans are O(|E|).

BFS Time Complexity

Explanation: Each vertex is enqueued and dequeued at most once and each edge is examined at most twice (undirected) or once (directed). Hence the algorithm runs in linear time in the size of the graph.

BFS Level Property

Explanation: Neighbors must lie in the same level or adjacent levels in a BFS tree. This is a key invariant that ensures correctness of discovered distances.

Manhattan Distance (4-neighbors)

Explanation: On a grid without obstacles and with 4-direction moves, the shortest path length equals the Manhattan distance. With obstacles, BFS is needed to respect walls while still minimizing steps.

Complexity Analysis

Multi-source BFS retains the classical BFS complexity. Using adjacency lists, we enqueue each source once at the start and then process each vertex at most once. For each dequeued vertex u, we scan all its adjacency entries. By the Handshake Lemma, the total number of adjacency entries is O() (2 for undirected graphs, for directed graphs). Thus the total time over the whole traversal is O( + ). Enqueueing multiple sources does not change the asymptotic bound; it simply shifts the starting frontier to include more vertices. Space complexity is O( + ) when using adjacency lists. We need O() to store the graph, O() for the distance array, O() for the visited/parent/sourc arrays (if kept), and O() in the worst case for the queue. In grid problems, the implicit graph can avoid explicit adjacency storage, bringing memory down to O(nm) for distances and the queue, where n × m is the grid size. If you also store parent pointers or source identifiers for path reconstruction or Voronoi labeling, space remains linear O(). The constants are small: each vertex typically holds a 32-bit distance, a parent index, and an 8/32-bit source id. Overall, multi-source BFS is among the most memory- and time-efficient tools for shortest paths in unweighted or uniformly weighted settings.

Code Examples

Compute distance to nearest source in an unweighted graph (multi-source BFS)
1#include <bits/stdc++.h>
2using namespace std;
3
4// Reads an undirected unweighted graph and a set of sources.
5// Outputs the distance from each vertex to its nearest source (or -1 if unreachable).
6// Input format:
7// n m
8// m lines: u v (1-based vertices)
9// k
10// k lines: s_i (1-based sources)
11// Example:
12// 6 6
13// 1 2
14// 2 3
15// 3 4
16// 4 5
17// 5 6
18// 1 6
19// 2
20// 1
21// 5
22int main() {
23 ios::sync_with_stdio(false);
24 cin.tie(nullptr);
25
26 int n, m;
27 if (!(cin >> n >> m)) return 0;
28 vector<vector<int>> adj(n);
29 for (int i = 0; i < m; ++i) {
30 int u, v; cin >> u >> v; --u; --v;
31 adj[u].push_back(v);
32 adj[v].push_back(u);
33 }
34 int k; cin >> k;
35 vector<int> src(k);
36 for (int i = 0; i < k; ++i) { cin >> src[i]; --src[i]; }
37
38 const int INF = 1e9;
39 vector<int> dist(n, INF);
40 queue<int> q;
41
42 // Initialize all sources with distance 0
43 for (int s : src) {
44 if (dist[s] == 0) continue; // avoid duplicates
45 dist[s] = 0;
46 q.push(s);
47 }
48
49 // Standard BFS expansion
50 while (!q.empty()) {
51 int u = q.front(); q.pop();
52 for (int v : adj[u]) {
53 if (dist[v] == INF) {
54 dist[v] = dist[u] + 1;
55 q.push(v);
56 }
57 }
58 }
59
60 // Print distances (use -1 for unreachable)
61 for (int v = 0; v < n; ++v) {
62 cout << (dist[v] == INF ? -1 : dist[v]) << (v + 1 == n ? '\n' : ' ');
63 }
64}
65

We push all sources into the queue with distance 0 and perform a standard BFS. The first time a node is discovered we assign its minimum distance to any source. If a vertex is never discovered, it is unreachable from all sources.

Time: O(n + m)Space: O(n + m)
Multi-source BFS on a grid with walls to compute time-to-reach from hazards
1#include <bits/stdc++.h>
2using namespace std;
3
4// Given an n x m grid with walls '#', empty '.', and hazard cells 'H',
5// compute the minimal time (steps) for hazard to reach each cell moving in 4 directions.
6// All hazard cells start simultaneously at time 0.
7// Input:
8// n m
9// n lines of grid
10// Output: the distance grid with -1 for unreachable cells (e.g., blocked components).
11
12int main() {
13 ios::sync_with_stdio(false);
14 cin.tie(nullptr);
15
16 int n, m;
17 if (!(cin >> n >> m)) return 0;
18 vector<string> g(n);
19 for (int i = 0; i < n; ++i) cin >> g[i];
20
21 const int INF = 1e9;
22 vector<vector<int>> dist(n, vector<int>(m, INF));
23 queue<pair<int,int>> q;
24
25 // Initialize sources: all 'H' cells
26 for (int i = 0; i < n; ++i) {
27 for (int j = 0; j < m; ++j) {
28 if (g[i][j] == 'H') {
29 dist[i][j] = 0;
30 q.push({i, j});
31 }
32 }
33 }
34
35 auto inside = [&](int x, int y){ return 0 <= x && x < n && 0 <= y && y < m; };
36 int dx[4] = {-1, 1, 0, 0};
37 int dy[4] = {0, 0, -1, 1};
38
39 while (!q.empty()) {
40 auto [x, y] = q.front(); q.pop();
41 for (int dir = 0; dir < 4; ++dir) {
42 int nx = x + dx[dir];
43 int ny = y + dy[dir];
44 if (!inside(nx, ny)) continue;
45 if (g[nx][ny] == '#') continue; // walls block movement
46 if (dist[nx][ny] != INF) continue; // already visited
47 dist[nx][ny] = dist[x][y] + 1;
48 q.push({nx, ny});
49 }
50 }
51
52 for (int i = 0; i < n; ++i) {
53 for (int j = 0; j < m; ++j) {
54 int ans = (dist[i][j] == INF ? -1 : dist[i][j]);
55 cout << ans << (j + 1 == m ? '\n' : ' ');
56 }
57 }
58}
59

We treat every hazard cell 'H' as a source. Obstacles '#' are impassable. The BFS wavefront advances one step per move, so dist[x][y] is the earliest time the hazard reaches cell (x, y). Empty cells unreachable due to walls keep distance -1.

Time: O(nm)Space: O(nm)
Assign nearest source and reconstruct a shortest path to it
1#include <bits/stdc++.h>
2using namespace std;
3
4// Multi-source BFS that also records which source reached each node first
5// and allows reconstructing a shortest path from a query node to its nearest source.
6// Input:
7// n m
8// m lines: u v (1-based, undirected)
9// k
10// k lines: s_i (1-based sources)
11// q (number of queries), followed by q nodes t_j to reconstruct paths for.
12// Output: for each query, either the path length and path, or -1 if unreachable.
13
14int main() {
15 ios::sync_with_stdio(false);
16 cin.tie(nullptr);
17
18 int n, m; if (!(cin >> n >> m)) return 0;
19 vector<vector<int>> adj(n);
20 for (int i = 0; i < m; ++i) {
21 int u, v; cin >> u >> v; --u; --v;
22 adj[u].push_back(v);
23 adj[v].push_back(u);
24 }
25 int k; cin >> k;
26 vector<int> sources(k);
27 for (int i = 0; i < k; ++i) { cin >> sources[i]; --sources[i]; }
28
29 const int INF = 1e9;
30 vector<int> dist(n, INF), parent(n, -1), src_id(n, -1);
31 queue<int> q;
32
33 // Initialize all sources: tie-break by smaller source index
34 // If duplicates exist, they are ignored by dist check.
35 for (int i = 0; i < k; ++i) {
36 int s = sources[i];
37 if (dist[s] == 0) continue;
38 dist[s] = 0;
39 parent[s] = -1; // root of its tree
40 src_id[s] = i; // remember which source index
41 q.push(s);
42 }
43
44 while (!q.empty()) {
45 int u = q.front(); q.pop();
46 for (int v : adj[u]) {
47 if (dist[v] == INF) {
48 dist[v] = dist[u] + 1;
49 parent[v] = u;
50 src_id[v] = src_id[u];
51 q.push(v);
52 }
53 }
54 }
55
56 int qn; cin >> qn;
57 while (qn--) {
58 int t; cin >> t; --t;
59 if (dist[t] == INF) {
60 cout << -1 << '\n';
61 continue;
62 }
63 // Reconstruct path from t back to its nearest source using parent[]
64 vector<int> path;
65 int cur = t;
66 while (cur != -1) {
67 path.push_back(cur);
68 cur = parent[cur];
69 }
70 reverse(path.begin(), path.end());
71 cout << "length=" << (int)path.size() - 1
72 << " source_index=" << src_id[t] << " path:";
73 for (int v : path) cout << ' ' << (v + 1);
74 cout << '\n';
75 }
76}
77

Beyond distances, we store parent pointers and the source index that first reached each vertex. This allows reconstructing a shortest path from any queried node to its nearest source and also yields a Voronoi labeling by source.

Time: O(n + m) for preprocessing, plus O(L) per query to output a path of length LSpace: O(n + m)