Solution: Redundant Connection
Let's solve the Redundant Connection problem using the Union Find pattern.
Statement#
We’re given an undirected graph consisting of nodes. The graph is represented as list called edges, of length , where edges[i] = [a, b] indicates that there is an edge between nodes a and b in the graph.
Return an edge that can be removed to make the graph a tree of nodes. If there are multiple candidates for removal, return the edge that occurs last in edges.
Constraints:
edges.lengthedges[i].length2-
ab - There are no repeated edges.
- The given graph is connected.
- The graph contains only one cycle.
Solution#
So far, you have probably brainstormed some approaches and have an idea of how to solve this problem. Let’s explore some of these approaches and figure out which one to follow based on considerations such as time complexity and any implementation constraints.
Naive approach#
We can solve this problem using techniques like DFS or BFS, however, the naive approach using DFS or BFS has a time complexity of in the worst-case scenario. This is because the DFS or BFS algorithm will explore the graph by visiting each node and its edges, which may result in visiting many nodes multiple times, and not necessarily finding the redundant connection in an efficient manner.
Optimized approach using union find#
We are going to solve this problem with the help of the union find pattern and will be using union by rank and path compression.
-
Union by rank: We connect the nodes that come afterward to the nodes that came before them. This allows us to add new nodes to the subset of the representative node of the larger connected component. Using ranks to connect nodes in this manner helps reduce the depth of the recursion call stack of the
findfunction, since the trees within each disjoint set remain relatively shallow. -
Path compression: On each
findoperation on a node of a tree, we update the parent of that node to point directly to the root. This reduces the length of the path of that node to the root, ensuring we don’t have to travel all the intermediate nodes on futurefindoperations.
The slides below illustrate how the algorithm runs:
1 of 10
2 of 10
3 of 10
4 of 10
5 of 10
6 of 10
7 of 10
8 of 10
9 of 10
10 of 10
Note: In the following section, we will gradually build the solution. Alternatively, you can skip straight to just the code.
Step by step solution construction#
We start with the basic implementation of Union Find (without the use of rank or path compression):
File: union_find.py
In the UnionFind class, we declare the parent list with length based on the edges list.
File: main.py
In the redundant_connection function, we’ll declare an object of the given class and initialize the parent list with default values.
File: union_find.py
Next, we need to check if the vertices forming an edge have the same parent. In the union function, we’ll call the parent of the first vertex p1 and the parent of the second vertex p2. The algorithm is described as follows:
-
If both
v1andv2have the same parent, i.e.,p1is equal top2, the given edge is redundant, so we return FALSE. -
Otherwise, this edge is connecting two vertices that were not already connected. So, we’ll update the
parentlist by making a connection based on the current edge and then return TRUE.
File: main.py
In the redundant_connection function, we traverse the edges list and for each edge, we attempt to connect its two vertices, v1, and v2, through the union(v1, v2) method.
We will now add union by rank and path compression to the Union Find algorithm. We’ll make the following changes to the UnionFind class:
Union by rank:
-
We initialize a
ranklist (set to the length of theedgeslist) with s. -
In the
unionfunction, if the parents,p1andp2of the vertices are not the same, we make the connection based on the ranks of both parents. This is done in the following way:-
If
p1's rank is greater thanp2's rank, we’ll updatep2's parent withp1, and addp2's rank top1's rank. For example, ifrank[p1]andrank[p2], we’ll addp2top1's set and update its rank to . -
Similarly, if
p2's rank is greater thanp1's rank, we’ll updatep1's parent withp2, and addp1's rank top2's rank.
-
Path compression:
- In the
findfunction, for a nodev, we make the found root as the parent ofvso that we don’t have to traverse all the intermediate nodes again on furtherfindoperations.
Lastly, we need to return the redundant edge. While traversing the edges list, for each edge, we will check if the union(v1, v2) method returns FALSE:
-
If it does, both
v1andv2will have the same parent. So the current edge will be redundant, and we return it. -
Otherwise,
v1andv2have different parents, so we connect them.
Just the code#
Here’s the complete solution to this problem:
Solution summary#
To recap, the solution to this problem can be divided into the following two main parts:
-
Initialize
parentandranklists based on the length of theedgeslist. -
Traverse the
edgeslist and for each edge, compare the parents of both vertices:-
If the parents are the same, the current edge is redundant, so we return it.
-
Otherwise, we connect the two vertices based on their respective ranks.
-
Time complexity#
The time complexity of this solution is , where is the number of edges.
Explanation:
-
We use a loop to traverse the
edgeslist, resulting in iterations. -
The time complexity of the the
UnionandFindfunctions is , since both path compression and union find by rank are being used. is almost constant time and grows very slowly with the input size, so the time complexity of both these functions can be considered as constant time for practical purposes.
Therefore, the overall time complexity becomes , which simplifies to .
Space Complexity#
The space complexity of this solution is .
Redundant Connection
Last Day Where You Can Still Cross