Hello! Welcome to your next lesson.
In our last session, you built a powerful UnionFind (or DSU) class, complete with path compression and union by rank optimizations. You learned how these optimizations lead to nearly constant-time find and union operations, making DSU an incredibly efficient tool.
Today, we'll put that tool to work. Your learning goal is to apply the DSU data structure to solve connectivity problems, specifically finding the number of "provinces" in a graph. This is a classic problem in competitive programming and a perfect first application for your new DSU skills.
1. Understanding the "Provinces" Problem
Imagine a map with several cities, where some cities are connected by roads. A "province" is a group of cities where you can travel from any city in the group to any other city in the same group, but you cannot travel to a city in a different province. The task is to count how many such separate provinces exist.
In graph theory terms, a province is a connected component.

While you could solve this using graph traversal algorithms like Depth-First Search (DFS) or Breadth-First Search (BFS), DSU offers a more direct and often more efficient approach, especially for dynamic connectivity scenarios.
2. The DSU Strategy for Counting Components
The core idea is to model the problem using disjoint sets. Here's how we map the graph problem to our DSU data structure:
- Initialization: We start by assuming each of the
nnodes (cities) is its own isolated province. So, we initialize our DSU withnelements, and our initial province count isn. - Processing Edges: We iterate through all the connections (edges) in the graph. For each edge connecting nodes
uandv, we perform aunion(u, v)operation. - Merging Provinces: The
unionoperation attempts to merge the sets containinguandv.- If
uandvwere in different sets (provinces), theunionoperation succeeds, and we have effectively merged two provinces into one. Therefore, we decrement our province count by 1. - If
uandvwere already in the same set, theunionoperation does nothing because they are already connected. The province count remains unchanged.
- If
- Final Count: After processing all the edges, the final value of our province counter is the answer.
This approach elegantly uses the boolean return value from the union method we designed in the last lesson to track the number of components dynamically.
3. A Step-by-Step Walkthrough
Let's watch a video that implements this exact logic to solve the "Number of Connected Components" problem. The problem is identical to finding the number of provinces.
Number of Connected Components in an Undirected Graph - Union Find - Leetcode 323 - Python
The video 'Number of Connected Components in an Undirected Graph' from the NeetCode channel provides an excellent walkthrough of this problem. It starts by explaining why Union-Find is a natural fit and then dives into the implementation.
Please watch the following segments: Why Union-Find? (04:07 - 05:44): This part explains the high-level idea of representing components as trees and merging them. Processing Edges (07:44 - 10:50): This is the core logic. Pay close attention to how the number of components starts at n and is decremented with each successful union. The Code (13:03 - 15:24): Watch how the union function's return value is used to update the component count inside the main loop.
4. The Python Implementation
As you saw in the video, the implementation is surprisingly concise once you have your UnionFind class. Let's assume you have the UnionFind class we built in the previous lesson. The function to count components would look like this:
# Assume this class is defined from the previous lesson
class UnionFind:
def __init__(self, n):
self.parent = list(range(n))
self.rank = [1] * n
def find(self, x):
if self.parent[x] != x:
self.parent[x] = self.find(self.parent[x]) # Path compression
return self.parent[x]
def union(self, x, y):
root_x = self.find(x)
root_y = self.find(y)
if root_x != root_y:
# Union by rank logic
if self.rank[root_x] > self.rank[root_y]:
self.parent[root_y] = root_x
elif self.rank[root_x] < self.rank[root_y]:
self.parent[root_x] = root_y
else:
self.parent[root_y] = root_x
self.rank[root_x] += 1
return True # Important: indicates a successful merge
return False # Indicates they were already connected
# The solution function
def count_provinces(n, edges):
"""
Counts the number of connected components in a graph.
Args:
n (int): The number of nodes (from 0 to n-1).
edges (list of lists): A list of edges, e.g., [[0, 1], [1, 2]].
"""
if n == 0:
return 0
uf = UnionFind(n)
num_components = n # Start with n components
for u, v in edges:
# If union is successful, it means we merged two previously
# separate components.
if uf.union(u, v):
num_components -= 1
return num_components
# Example usage:
nodes = 5
connections = [[0, 1], [1, 2], [3, 4]]
print(f"Number of provinces: {count_provinces(nodes, connections)}") # Output: 2
This code directly implements the strategy we discussed. It initializes the count to n and decrements it every time the union function confirms that two separate components have been merged.
Test your understanding!
Consider a graph with n = 6 nodes and the following list of edges: [[0, 1], [0, 2], [1, 2], [3, 4]].
Trace the count_provinces algorithm. What is the value of num_components after processing each edge?
Show answer
- Initial state:
num_components = 6. - Process edge
[0, 1]:union(0, 1)is successful.num_componentsbecomes5. - Process edge
[0, 2]:union(0, 2)is successful.num_componentsbecomes4. (Now {0, 1, 2} is one component). - Process edge
[1, 2]:find(1)andfind(2)return the same root.union(1, 2)is unsuccessful (returnsFalse).num_componentsremains4. - Process edge
[3, 4]:union(3, 4)is successful.num_componentsbecomes3.
The final answer is 3. The components are {0, 1, 2}, {3, 4}, and {5}.
5. An Alternative Counting Method
There's another common way to count the components, which is conceptually simple but might be slightly less efficient as it requires a final loop. Instead of decrementing a counter, you can wait until all union operations are done and then count the number of unique roots.
Part 28 Understanding Union Find in Python
This article from Medium, 'Understanding Union Find in Python', shows this alternative counting method in its solution to the 'Number of Connected Components' problem. Let's look at the implementation.
Scroll down to the 'Easy Problem: Number of Connected Components...' section. Look at the last line of the countComponents function: return len(set(uf.find(x) for x in range(n))).
This line of code does the following:
- It iterates through every node
xfrom0ton-1. - For each node, it calls
uf.find(x)to get the root of its component. - It adds all these roots to a
set, which automatically handles duplicates. - The final number of components is simply the size (
len) of the set of unique roots.
Both methods are correct and widely used. The decrementing-counter method is slightly more performant as it avoids the final loop, but the unique-root-counting method is also perfectly acceptable and easy to remember.
Conclusion
You have successfully applied your DSU data structure to a practical graph problem. This pattern of initializing n components and merging them based on edge connections is fundamental to solving many connectivity-based problems in competitive programming.
Key Takeaways:
- A connected component (or "province") in a graph can be modeled as a set in the DSU data structure.
- The strategy is to initialize
ncomponents and process each edge with aunionoperation. - You can count components by either:
- Decrementing a counter each time a
unionoperation successfully merges two components. - Counting the number of unique roots after all
unionoperations are complete.
- Decrementing a counter each time a
- DSU provides an elegant and efficient solution for this type of problem.
Up Next:
In the next lesson, we will continue exploring the power of DSU by tackling another critical application: implementing Kruskal's algorithm to find a Minimum Spanning Tree (MST). You'll see how DSU's ability to efficiently detect if two nodes are already connected is the key to building the MST.