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 lists — You must know how to represent and iterate neighbors efficiently.
- →Single-source BFS — Multi-source BFS extends BFS; understanding the single-source version is essential.
- →Queues and FIFO behavior — BFS correctness relies on processing vertices in first-in-first-out order.
- →Grid traversal and boundary checking — Many practical problems occur on 2D grids with obstacles and require careful index handling.
- →Shortest paths in unweighted graphs — Understanding 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 definitions01Overview
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
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
Code Examples
1 #include <bits/stdc++.h> 2 using 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 22 int 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.
1 #include <bits/stdc++.h> 2 using 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 12 int 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.
1 #include <bits/stdc++.h> 2 using 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 14 int 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.