Hello! Welcome back to our module on graph traversal algorithms.
In the last lesson, we saw how to apply Depth-First Search (DFS) to detect cycles in both undirected and directed graphs. We learned that the core DFS traversal can be adapted with simple additions—like tracking a parent node or using a three-state system—to solve complex problems. A key pattern we used was iterating through all nodes to handle potentially disconnected graphs, which is the perfect foundation for today's topic.
Today, your goal is to learn how to find connected components in an undirected graph. This is a fundamental problem that helps determine the structure of a graph: is it one single network, or is it composed of several separate "islands" of nodes?
1. What are Connected Components?
An undirected graph is made up of one or more connected components. A connected component is a subgraph where there is a path between any two nodes within that component, but no path to any node outside of it.
Think of it like a social network. If you can trace a path of friendships from person A to person B, they are in the same component. If there's no possible chain of friends connecting person A to person C, then A and C are in different components.

Our task is to write an algorithm that counts how many of these separate "islands" exist in a given graph.
2. The Core Algorithm: Traversal with a Master Loop
In our previous lessons, we often started a DFS or BFS from a single source node (e.g., node 0). But what happens if the graph is disconnected, like the one above? A single traversal starting from node 1 would only visit nodes {1, 2, 3, 4}. It would never reach nodes {5, 6, 7, 8}.
So, how do we ensure we visit every node and count every component? The solution is beautifully simple and builds on what we already know.
To understand the high-level strategy, let's watch a short conceptual explanation.
G-4. What are Connected Components ?
The video 'What are Connected Components?' from the 'take U forward' channel clearly explains why a single traversal isn't enough for a disconnected graph and introduces the standard pattern to overcome this.
Watch from 02:33 to 06:38. Focus on the main idea: using a loop that iterates through all nodes and a visited array to launch a new traversal only when an unvisited node is found.
As the video explained, the master algorithm is:
- Initialize a
component_countto 0. - Create a
visitedset to keep track of nodes we've already seen. - Loop through every node from
0ton-1. - For each node, if it has not been visited yet:
- We have found a new component! Increment
component_count. - Start a traversal (like DFS or BFS) from this node. This traversal will find and mark all nodes in the current component as visited.
- We have found a new component! Increment
- After the loop finishes,
component_countwill hold the total number of connected components.
3. Algorithm Walkthrough with DFS
Now, let's see this algorithm in action using DFS as our traversal method. The following video provides a step-by-step walkthrough of a concrete example.
NUMBER OF CONNECTED COMPONENTS IN AN UNDIRECTED GRAPH | LEETCODE # 323 | PYTHON GRAPH SOLUTION
The video 'NUMBER OF CONNECTED COMPONENTS...' from 'Cracking FAANG' demonstrates the full process, from building the graph to counting the components.
Watch the segment from 01:41 to 06:18. Pay close attention to how the visited set is populated during the DFS and how the component_count is incremented only when the main loop finds an unvisited node.
To solidify your understanding, let's turn to a well-structured written explanation that breaks down the intuition and implementation steps.
323. Number of Connected Components in an Undirected Graph
The article 'Number of Connected Components in an Undirected Graph' from Algo.monster provides a concise and clear summary of the logic, a step-by-step example, and a clean Python implementation.
Read the sections 'Intuition', 'Solution Approach', and 'Example Walkthrough'. This will reinforce the 'islands' analogy and show you exactly how the variables change as the algorithm runs.
4. Python Implementation
Now let's put all the pieces together into a single Python function. The implementation below is based on the approach from the Algo.monster resource, which is clean and efficient.
from collections import defaultdict
def count_components(n: int, edges: list[list[int]]) -> int:
"""
Counts the number of connected components in an undirected graph.
Args:
n: The total number of nodes, labeled 0 to n-1.
edges: A list of pairs representing undirected edges.
Returns:
The number of connected components.
"""
# Step 1: Build the adjacency list representation of the graph.
# We use a defaultdict to easily append to lists.
adj = defaultdict(list)
for u, v in edges:
adj[u].append(v)
adj[v].append(u)
# Step 2: Initialize a set to track all visited nodes across all components.
visited = set()
component_count = 0
def dfs(node: int):
"""A simple DFS to find all nodes in a component."""
visited.add(node)
for neighbor in adj[node]:
if neighbor not in visited:
dfs(neighbor)
# Step 3: The master loop. Iterate through every possible node.
for i in range(n):
# If the node hasn't been visited, it's the start of a new component.
if i not in visited:
component_count += 1
# Start DFS to find all nodes in this new component.
dfs(i)
return component_count
# --- Example Usage ---
# Graph 1: Two components {0, 1, 2} and {3, 4}
n1 = 5
edges1 = [[0, 1], [1, 2], [3, 4]]
print(f"Graph 1 has {count_components(n1, edges1)} components.") # Expected: 2
# Graph 2: One component {0, 1, 2, 3} and one isolated node {4}
# Note that node 4 is a component by itself.
n2 = 5
edges2 = [[0, 1], [1, 2], [2, 3]]
print(f"Graph 2 has {count_components(n2, edges2)} components.") # Expected: 2
Test your understanding!
Consider a graph with n = 7 nodes and edges [[0, 1], [1, 2], [3, 4], [5, 6]].
Trace the count_components algorithm. What will the final component_count be?
Show answer
The final component_count will be 3.
- Loop starts at
i = 0:0is not invisited.component_countbecomes 1.dfs(0)is called. It visits0, then1, then2.visitedis now{0, 1, 2}.
- Loop continues,
i = 1,i = 2: Both are invisited, so nothing happens. - Loop continues,
i = 3:3is not invisited.component_countbecomes 2.dfs(3)is called. It visits3, then4.visitedis now{0, 1, 2, 3, 4}.
- Loop continues,
i = 4: It's invisited, skip. - Loop continues,
i = 5:5is not invisited.component_countbecomes 3.dfs(5)is called. It visits5, then6.visitedis now{0, 1, 2, 3, 4, 5, 6}.
- Loop continues,
i = 6: It's invisited, skip. - The loop finishes. The function returns
component_count, which is 3.
The three components are {0, 1, 2}, {3, 4}, and {5, 6}.
5. Complexity and Competitive Programming Pitfalls
Understanding the performance and common mistakes is crucial for competitive programming.
Complexity Analysis
-
Time Complexity: , where is the number of nodes (
n) and is the number of edges.- Building the adjacency list takes time.
- The master loop runs times. The DFS function is called once for each component. Across all calls, each node and edge is visited exactly once. Therefore, the total time for all traversals is .
- The overall complexity is dominated by these two parts, giving us .
-
Space Complexity:
- The adjacency list stores entries in total for an undirected graph, so it takes space. If we consider isolated nodes, the list itself takes space. Thus, .
- The
visitedset can store up to nodes, taking space. - The recursion stack for DFS can go as deep as in the worst case (a long chain), taking space.
- The total space is therefore .
Common Pitfalls
It's easy to make small mistakes when implementing this. Let's review some common traps to avoid.
323. Number of Connected Components in an Undirected Graph
The 'Common Pitfalls' section of the Algo.monster article is excellent for competitive programmers. It highlights subtle but critical errors that can lead to wrong answers or timeouts.
Read the 'Common Pitfalls' section carefully. Pay special attention to 'Forgetting to Handle Isolated Nodes' and 'Using Wrong Data Structure for Visited Tracking', as these are frequent sources of bugs.
Conclusion
Congratulations! You've successfully learned how to find and count the connected components of an undirected graph. This pattern of combining a master loop with a graph traversal is a powerful tool in your competitive programming arsenal.
Key Takeaways:
- A connected component is a maximal set of nodes where every node is reachable from every other node in that set.
- For graphs that might be disconnected, a single traversal from one source is not enough.
- The standard algorithm is to iterate through all nodes from
0ton-1. If a node hasn't been visited, you've found a new component, so you increment a counter and start a traversal (DFS or BFS) to mark all nodes within it. - The time and space complexity of this approach is .
Up Next:
We've applied our traversal skills to cycle detection and finding connected components. In the next lesson, we will move to another important graph algorithm: Topological Sorting. This is used for ordering tasks or events that have dependencies, and it applies specifically to Directed Acyclic Graphs (DAGs).