LC323. Number of Connected Components in an Undirected Graph¶
Problem Description¶
LeetCode Problem 323:
You have a graph of n
nodes. You are given an integer n
and an array edges
where
edges[i] = [ai, bi]
indicates that there is an edge between ai
and bi
in the graph.
Return the number of connected components in the graph.
Clarification¶
- Undirected graph
Assumption¶
- node values range from 0 to n - 1
Solution¶
Approach 1 - Union Find¶
We can use union find to count the number of connected components in the graph.
class UnionFind:
def __init__(self, n):
self.root = [i for i in range(n)]
self.rank = [0] * n
self.count = n
def find(self, x):
if x == self.root[x]:
return x
self.root[x] = self.find(self.root[x])
return self.root[x]
def union(self, x, y):
root_x = self.find(x)
root_y = self.find(y)
if root_x != root_y:
if self.rank[root_x] > self.rank[root_y]:
self.root[root_y] = root_x
elif self.rank[root_x] < self.rank[root_y]:
self.root[root_x] = root_y
else:
self.root[root_y] = root_x
self.rank[root_x] += 1
self.count -= 1
class Solution:
def countComponents(self, n: int, edges: List[List[int]]) -> int:
uf = UnionFind(n)
for edge in edges:
uf.union(edge[0], edge[1])
return uf.count
Complexity Analysis of Approach 1¶
- Time complexity: \(O(V + E \alpha(V))\) where \(V\) is number of vertices and \(E\) is number of edges
- Initialize
root
andrank
arrays take \(O(V)\) time - The for-loop requires \(O(E)\) operations to iterating all edges. Each iteration calls
union
function and takes \(O(\alpha(V))\).
So the total time complexity is \(O(V + E \alpha(V))\).
- Initialize
- Space complexity: \(O(V)\)
The union find takes \(O(V)\) space to storesroot
andrank
.
Approach 2 - DFS/BFS¶
The problem can also be solved using either BFS or DFS. Start from a vertex and traverse the sub-graph (connected components) using either BFS or DFS and mark nodes visited. For the next node not visited, traverse and mark visited again. In the meantime, increase count by 1 since it doesn't belong to previous connected components.
class Solution:
def countComponents(self, n: int, edges: List[List[int]]) -> int:
count = 0
# Convert edge list to adjacent list for easy traversing later
adj_list = [[] for _ in range(n)]
for edge in edges:
adj_list[edge[0]].append(edge[1])
adj_list[edge[1]].append(edge[0])
visited = set()
for node in range(n):
if node not in visited:
count += 1
self.dfs(node, adj_list, visited)
return count
def dfs(self, node: int, adj_list: List[List[int]], visited: Set[int]) -> None:
visited.add(node)
for neighbor in adj_list[node]:
if neighbor not in visited:
self.dfs(neighbor, adj_list, visited)
class Solution:
def countComponents(self, n: int, edges: List[List[int]]) -> int:
count = 0
# Convert edge list to adjacent list for easy traversing later
adj_list = [[] for _ in range(n)]
for edge in edges:
adj_list[edge[0]].append(edge[1])
adj_list[edge[1]].append(edge[0])
visited = set()
for node in range(n):
if node not in visited:
count += 1
self.bfs(node, adj_list, visited)
return count
def bfs(self, node: int, adj_list: List[List[int]], visited: Set[int]) -> None:
visited.add(node)
queue = deque([node])
while queue:
curr_node = queue.popleft()
for neighbor in adj_list[curr_node]:
if neighbor not in visited:
queue.append(neighbor)
visited.add(neighbor)
Complexity Analysis of Approach 2¶
- Time complexity: \(O(V + E)\)
- Initialize
adj_list
takes \(O(V)\) time - Convert edge list to
adj_list
takes \(O(E)\) time - During DFS/BFS traversal, each vertex will only be visited once, taking \(O(V)\) time. For each vertex, we iterate its edge list once. In total, takes \(O(E)\) time to go through all edges. So the time complexity for traversing is \(O(V + E)\).
So the total time complexity is \(O(V) + O(E) + O(V + E) = O(V + E)\).
- Initialize
- Space complexity: \(O(V + E)\)
adj_list
takes \(O(V + E)\) space since store all nodes and their edges.visited
takes \(O(V)\) space to track nodes- The call stack of DFS or queue of BFS takes \(O(V)\) space in the worst case.
So the total space complexity is \(O(V + E) + O(V) + O(V) = O(V + E)\).
Comparison of Different Approaches¶
The table below summarize the time complexity and space complexity of different approaches:
Approach | Time Complexity | Space Complexity |
---|---|---|
Approach 1 - Union Find | \(O(V + E \alpha(V))\) | \(O(V)\) |
Approach 2 - BFS/DFS | \(O(V + E)\) | \(O(V + E)\) |