Connected components
To split an undirected graph or a grid into connected components, start a search from every vertex that no earlier search has reached. This counts the components, measures their sizes and tells whether two vertices are in the same one, all in O(n + m). If the edges are added one by one between the questions, use a disjoint set union instead.
Explanation
Five people live in a small town, and some of them are friends: 1 and 2, 2 and 3, 4 and 5. Tell person 1 a rumour and watch where it goes. Two people had to be told, because the town has two groups of friends.
Fig. 1. 5 people, friendships 1–2, 2–3, 4–5.
In a bigger crowd the groups are harder to see. Here are 9 people and 7 friendships. How many people must you tell so that all 9 hear the rumour?
Fig. 2. 9 people, 7 friendships.
One rumour told to person 1 finds everyone who will hear it from person 1, the way breadth-first search finds everything reachable from its start. The obvious plan is to tell each of the 9 people a rumour in turn and see which different groups come out. Persons 1, 4, 7 and 8 each find the same group of four. Persons 2, 3 and 6 each find the same group of three, and persons 5 and 9 each find the same pair. That is 9 rumours and 29 people reached for only 3 groups, and the groups still have to be compared to see which are the same.
Every rumour after the first one in a group is wasted, and you can tell in advance which ones. A rumour reaches everyone in its group, so a person who has already heard is in a group that has been found. So go through the people in order, from 1 to 9. If a person has already heard a rumour, skip them. If not, tell them a new rumour, let it spread from friend to friend, and write its number next to everyone it reaches. When the last person has been checked, the number of rumours started is the answer.
A group of people who can all reach each other through friends is a connected component, and each rumour finds exactly one.
Nobody has heard a rumour yet, so nobody has a number.
Fig. 3. The scan of the 9 people. The arrow points at the person the scan checks, and the row of boxes holds the number of each person.
Three people must be told: persons 1, 2 and 5. Every person also got the number of their group: persons 1, 4, 7 and 8 got 1, persons 2, 3 and 6 got 2, and persons 5 and 9 got 3. So the groups have 4, 3 and 2 people. Two people hear each other's rumours exactly when their numbers are equal. Persons 4 and 8 do, since both have 1. Persons 3 and 8 don't, since they have 2 and 1.
Islands
On a map of land and water, each island is a group, and two land squares that share a side are friends. The same loop goes through the squares row by row and starts a new search at every land square that has no number yet.
Fig. 4. The scan of the whole map.
Implementation
In code, people are vertices and friendships are edges. A vertex is reached once it has a number.
Let's keep the numbers in an array comp: comp[v] is the number of the component of v, and comp[v] == 0 while v is not reached. This one array is both the mark of the search and the answer. The variable count is the number of searches started so far.
Initialise:
comp[v] = 0for every vertexv, andcount = 0.Start a new component: go through
s= 1, 2, …,n. Ifcomp[s] == 0, increasecount, setcomp[s] = countand pushsto an empty queue.Take the next vertex
vfrom the front of the queue.Expand its edges: look at each neighbour
uofv.Reach it: if
comp[u] == 0, setcomp[u] = countand pushu.Stop: the search stops when the queue is empty, and the loop goes on to the next
s. The loop stops after vertexn.
The program reads n and m, then m lines u v: a two-way edge between u and v. Vertices are numbered from 1 to n. The program prints the number of components, then comp[v] for every vertex, then the size of every component. For the crowd of 9 the input is:
9 7
1 4
4 7
7 8
2 3
2 6
3 6
5 9The graph is stored as an adjacency list, where adj[v] lists the neighbours of v. Each edge goes into both lists. The search inside the loop is the BFS you know.
#include <iostream>
#include <queue>
#include <vector>
using namespace std;
int main() {
int n, m;
cin >> n >> m;
vector<vector<int>> adj(n + 1);
for (int i = 0; i < m; i++) {
int u, v;
cin >> u >> v;
adj[u].push_back(v);
adj[v].push_back(u);
}
// initialise
vector<int> comp(n + 1, 0);
int count = 0;
vector<int> size(n + 1, 0); // only needed for the sizes
for (int s = 1; s <= n; s++) {
if (comp[s] != 0) continue; // already reached: skip
// start a new component
count++;
comp[s] = count;
size[count]++; // only needed for the sizes
queue<int> q;
q.push(s);
while (!q.empty()) { // stop: when the queue is empty
// take the next vertex
int v = q.front();
q.pop();
// expand its edges
for (int u : adj[v]) {
if (comp[u] == 0) {
// reach it
comp[u] = count;
size[count]++; // only needed for the sizes
q.push(u);
}
}
}
}
cout << count << '\n';
for (int v = 1; v <= n; v++) {
cout << comp[v] << (v < n ? ' ' : '\n');
}
// print the size of every component; remove this block if you only need the numbers
for (int k = 1; k <= count; k++) {
cout << size[k] << (k < count ? ' ' : '\n');
}
}A recursive DFS, shorter to write, can replace the BFS inside the loop. Only the search changes: every vertex gets the same number, though the order inside a component may differ.
Over all searches together, each vertex gets a number once and is taken from the queue once, each edge is checked twice, once from each end, and the loop itself checks n vertices. So the program takes O(n + m) time and O(n + m) memory, however many searches there are.
Sizes
The program counts the size of each component while numbering: it adds 1 to size[count] every time a vertex gets the number count, in the lines marked "only needed for the sizes". There are at most n components, so size has n + 1 cells. On the crowd of 9 the last line of the output is 4 3 2.
To list the members of each component, keep vector<vector<int>> members(n + 1) instead and push each vertex into members[count] when it gets its number.
Checking two vertices
After the loop, "are u and v in the same component?" is the check comp[u] == comp[v], one comparison per question.
Grids
On the island map, # is land and . is water. A vertex is a land square (r, c), and its neighbours are the land squares that share a side with it. The grid has R rows and C columns, and g holds its rows as strings. Only these lines change: comp is two-dimensional, the loop goes over all squares, and the search checks the four side neighbours.
int dr[] = {-1, 1, 0, 0}; // up, down, left, right
int dc[] = {0, 0, -1, 1};
// initialise
vector<vector<int>> comp(R, vector<int>(C, 0));
int count = 0;
for (int sr = 0; sr < R; sr++) {
for (int sc = 0; sc < C; sc++) {
if (g[sr][sc] != '#' || comp[sr][sc] != 0) continue; // water or reached
// start a new component
count++;
comp[sr][sc] = count;
queue<pair<int, int>> q;
q.push({sr, sc});
while (!q.empty()) {
// take the next vertex
auto [r, c] = q.front();
q.pop();
// expand its edges
for (int k = 0; k < 4; k++) {
int nr = r + dr[k], nc = c + dc[k];
if (nr < 0 || nr >= R || nc < 0 || nc >= C) continue; // outside the grid
if (g[nr][nc] != '#') continue; // water
if (comp[nr][nc] == 0) {
// reach it
comp[nr][nc] = count;
q.push({nr, nc});
}
}
}
}
}On the island map this finds 4 islands of 3, 5, 3 and 3 squares. The time and the memory are O(R \cdot C). A 1000 \times 1000 map has 10^6 squares, so BFS makes at most about 4 \cdot 10^6 neighbour checks. A recursive search on such a map can run out of memory for calls, as the DFS article explains.
Correctness proof
When the search with number ends, the vertices with number are exactly the vertices that can be reached from its start. This is what BFS does, as the BFS article shows. A search never changes a number given by an earlier search, because it reaches only vertices with comp_u = 0.
Same number means same component. Two vertices with number can both be reached from the start of search , so they can reach each other through it.
Same component means same number. Take two vertices and in one component, and say got its number first, from search . Search reaches everything that can be reached from its start, and can be reached from , so gets the number too.
The count is right. A new search starts only at a vertex with no number, and by the argument above that vertex is in no component found so far. So each search finds a new component. The loop checks every vertex, so every vertex ends up with a number, and no component is missed.
Common mistakes
Starting a search from every vertex, or clearing
compbefore each search, so no vertex is ever skipped and the count comes out asn. Skip a vertex that already has a number, and fillcompwith zeros only once, before the loop.Counting components as n - m, which is right only when no component has a cycle: an edge on a cycle joins two vertices already in one component. Count the searches instead.
Storing a two-way edge in one list only, with
adj[v].push_back(u);left out, so the search follows an edge only from its first vertex and splits a component in several. Add every edge to both lists.Starting a new search for every "same component?" question, which can walk a whole component each time. Number the vertices once, before the questions, and compare
comp[u]andcomp[v].Not clearing
adjandcompwhen the input has several test cases, so old edges and old numbers carry over into the next test. Calladj.assign(n + 1, {})andcomp.assign(n + 1, 0)at the start of every test.
Similar algorithms
Disjoint set union is for edges that arrive one by one, with questions between them, such as "after each new road, how many components are there?". Running the loop again after every edge costs O(n + m) each time. A disjoint set union merges the two components of each new edge in almost constant time, without searching again.
Strongly connected components are for one-way edges. There, "can reach" doesn't work both ways, and the count from this loop depends on the order of the vertices. With the single edge 2 \to 1 it counts 2 components, and with 1 \to 2 it counts 1. Components where everyone can reach everyone along the arrows need a different algorithm.
Practice
Easy · Graphs · Depth-first Search
Easy · Disjoint set union · Graphs
Very easy · Graphs · Implementation
Very easy · Graphs
Very easy · Disjoint set union · Graphs
Medium · Depth-first Search · Graphs