A connected component is a maximal set of vertices that can all reach each other. Finding them is the “hello world” of graph algorithms and the first thing to check when a problem smells graph-shaped.
By DFS or BFS —
vector<int> comp; // comp[v] = component id, -1 if unassigned
int numComponents = 0;
void dfsComp(int u, int id) {
comp[u] = id;
for (int v : adj[u])
if (comp[v] == -1) dfsComp(v, id);
}
void findComponents(int n) {
comp.assign(n, -1);
numComponents = 0;
for (int i = 0; i < n; i++)
if (comp[i] == -1) dfsComp(i, numComponents++);
}By DSU —
Preferable when edges arrive online, when you also need component sizes, or when the graph is given only as an edge list.
DSU dsu(n);
for (auto [u, v] : edges) dsu.unite(u, v);
// components = number of distinct dsu.find(i)
// size of v's component = dsu.size[dsu.find(v)]See Disjoint Set Union.
Which to use
| 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 |
Directed graphs
Running undirected component finding on a directed graph gives weakly connected components (connectivity ignoring direction). If the problem cares about reachability in the direction of the edges, you want strongly connected components instead. Confusing the two is a classic wrong-answer.
Things components immediately give you
- Is the graph connected? One component.
- Minimum edges to connect the graph: .
- Number of spanning forests / can a spanning tree exist: a spanning tree exists iff there is one component.
- Bipartiteness is checked per component — see Bipartite Graphs.
- Component sizes answer “how many pairs of vertices are connected”: .
- Grid flood fill — islands, regions, paint bucket; the same algorithm with an implicit graph.
Flood fill on a grid
int islands(vector<string>& g) {
int R = g.size(), C = g[0].size(), cnt = 0;
int dr[4] = {-1,1,0,0}, dc[4] = {0,0,1,-1};
for (int i = 0; i < R; i++)
for (int j = 0; j < C; j++) {
if (g[i][j] != '1') continue;
cnt++;
queue<pair<int,int>> q; q.push({i, j}); g[i][j] = '0';
while (!q.empty()) {
auto [r, c] = q.front(); q.pop();
for (int d = 0; d < 4; d++) {
int rr = r + dr[d], cc = c + dc[d];
if (rr < 0 || cc < 0 || rr >= R || cc >= C) continue;
if (g[rr][cc] != '1') continue;
g[rr][cc] = '0';
q.push({rr, cc});
}
}
}
return cnt;
}Marking the cell as visited when pushing (here by overwriting it) rather than when popping keeps the queue .