Design a disjoint set (also called union-find) data structure that supports the following operations:
DisjointSet(int n) initializes the disjoint set with n elements.
void unionByRank(int u, int v) merges the sets containing u and v using the rank heuristic.
void unionBySize(int u, int v) merges the sets containing u and v using the size heuristic.
bool find(int u, int v) checks if the elements u and v are in the same set and returns true if they are, otherwise false.
Input:
["DisjointSet", "unionByRank", "unionBySize", "find", "find"]
[[5], [0, 1], [2, 3], [0, 1], [0, 3]]
Output:
[null, null, null, true, false]
Explanation:
DisjointSet ds = new DisjointSet(5); // Initialize a disjoint set with 5 elements
ds.unionByRank(0, 1); // Merge sets containing 0 and 1 using rank
ds.unionBySize(2, 3); // Merge sets containing 2 and 3 using size
ds.find(0, 1); // Returns true as 0 and 1 are in the same set
ds.find(0, 3); // Returns false as 0 and 3 are not in the same set
Input:
["DisjointSet", "unionBySize", "unionBySize", "find", "find"]
[[3], [0, 1], [1, 2], [0, 2], [0, 1]]
Output:
[null, null, null, true, true]
Explanation:
DisjointSet ds = new DisjointSet(3); // Initialize a disjoint set with 3 elements
ds.unionBySize(0, 1); // Merge sets containing 0 and 1 using size
ds.unionBySize(1, 2); // Merge sets containing 1 and 2 using rank
ds.find(0, 2); // Returns true as 0 and 2 are in the same set
ds.find(0, 1); // Returns true as 0 and 1 are in the same set
Input:
["DisjointSet", "unionByRank", "unionBySize", "unionByRank", "find", "find"]
[[5], [0, 1], [3, 4], [1, 2], [0, 2], [1, 3]]
The Disjoint Set data structure is a crucial topic in the graph series. To understand its necessity, consider the following problem:
{{1,2},{2,3},{4,5},{6,7},{5,6},{3,7}}
The Disjoint Set data structure provides two primary functionalities:
To implement Union by rank, two arrays of size N (number of nodes) are needed:
#include <bits/stdc++.h>
using namespace std;
class DisjointSet {
private:
// To store the rank of each node
vector<int> rank;
/* To store the size of components
that each node belongs to */
vector<int> size;
// To store the ultimate parent of each node
vector<int> parent;
public:
// Constructor
DisjointSet(int n) {
// Resize the arrays
rank.resize(n + 1, 0);
parent.resize(n + 1);
size.resize(n+1, 1);
// Initialise each node with its own value
for (int i = 0; i <= n; i++) {
parent[i] = i;
}
}
/* Helper function to find ultimate
parent along with path compression */
int findUPar(int node) {
// Base case
if (node == parent[node])
return node;
// Backtracking step for path compression
return parent[node] = findUPar(parent[node]);
}
/* Function to detemine if two nodes
are in the same component or not */
bool find(int u, int v) {
// Return true if both have same ultimate parent
return (findUPar(u) == findUPar(v));
}
/* Function to perform union of
two nodes based on ranks */
void unionByRank(int u, int v) {
// Get the ultimate parents of both nodes
int ulp_u = findUPar(u);
int ulp_v = findUPar(v);
// Return if nodes already belong to the same component
if (ulp_u == ulp_v) return;
/* Otherwise, join the node to the other
node having higher ranks among the two */
if (rank[ulp_u] < rank[ulp_v]) {
// Update the parent
parent[ulp_u] = ulp_v;
}
else if (rank[ulp_v] < rank[ulp_u]) {
// Update the parent
parent[ulp_v] = ulp_u;
}
/* If both have same rank, join any node to the
other and increment the rank of the parent node */
else {
// Update the parent
parent[ulp_v] = ulp_u;
// Update the rank
rank[ulp_u]++;
}
}
/* Function to perform union of
two nodes based on ranks */
void unionBySize(int u, int v) {
// Get the ultimate parents of both nodes
int ulp_u = findUPar(u);
int ulp_v = findUPar(v);
// Return if nodes already belong to the same component
if (ulp_u == ulp_v) return;
/* Otherwise, join the node belonging to the smaller
component to the node belonging to bigger component */
if (size[ulp_u] < size[ulp_v]) {
// Update the parent
parent[ulp_u] = ulp_v;
// Update the size
size[ulp_v] += size[ulp_u];
}
else {
// Update the parent
parent[ulp_v] = ulp_u;
// Update the size
size[ulp_u] += size[ulp_v];
}
}
};
int main() {
// Disjoint Data structure
DisjointSet ds(7);
ds.unionByRank(1, 2); // Adding edge between 1 and 2
ds.unionByRank(2, 3); // Adding edge between 2 and 3
ds.unionByRank(4, 5); // Adding edge between 4 and 5
ds.unionByRank(6, 7); // Adding edge between 6 and 7
ds.unionByRank(5, 6); // Adding edge between 5 and 6
/* Checking if node 3 and node 7
are in the same component */
if (ds.find(3, 7))
cout << "They belong to the same components.\n";
else
cout << "They do not belong to the same components.\n";
ds.unionByRank(3, 7); // Adding edge between 3 and 7
/* Checking again if node 3 and node 7
are in the same component */
if (ds.find(3, 7))
cout << "They belong to the same components.\n";
else
cout << "They do not belong to the same components.\n";
return 0;
}
import java.util.*;
class DisjointSet {
// To store the rank of each node
private int[] rank;
/* To store the size of components
that each node belongs to */
private int[] size;
// To store the ultimate parent of each node
private int[] parent;
// Constructor
public DisjointSet(int n) {
// Resize the arrays
rank = new int[n + 1];
parent = new int[n + 1];
size = new int[n + 1];
Arrays.fill(size, 1);
// Initialise each node with its own value
for (int i = 0; i <= n; i++) {
parent[i] = i;
}
}
/* Helper function to find ultimate
parent along with path compression */
public int findUPar(int node) {
// Base case
if (node == parent[node])
return node;
// Backtracking step for path compression
return parent[node] = findUPar(parent[node]);
}
/* Function to determine if two nodes
are in the same component or not */
public boolean find(int u, int v) {
// Return true if both have same ultimate parent
return (findUPar(u) == findUPar(v));
}
/* Function to perform union of
two nodes based on ranks */
public void unionByRank(int u, int v) {
// Get the ultimate parents of both nodes
int ulp_u = findUPar(u);
int ulp_v = findUPar(v);
// Return if nodes already belong to the same component
if (ulp_u == ulp_v) return;
/* Otherwise, join the node to the other
node having higher ranks among the two */
if (rank[ulp_u] < rank[ulp_v]) {
// Update the parent
parent[ulp_u] = ulp_v;
} else if (rank[ulp_v] < rank[ulp_u]) {
// Update the parent
parent[ulp_v] = ulp_u;
}
/* If both have same rank, join any node to the
other and increment the rank of the parent node */
else {
// Update the parent
parent[ulp_v] = ulp_u;
// Update the rank
rank[ulp_u]++;
}
}
/* Function to perform union of
two nodes based on sizes */
public void unionBySize(int u, int v) {
// Get the ultimate parents of both nodes
int ulp_u = findUPar(u);
int ulp_v = findUPar(v);
// Return if nodes already belong to the same component
if (ulp_u == ulp_v) return;
/* Otherwise, join the node belonging to the smaller
component to the node belonging to bigger component */
if (size[ulp_u] < size[ulp_v]) {
// Update the parent
parent[ulp_u] = ulp_v;
// Update the size
size[ulp_v] += size[ulp_u];
} else {
// Update the parent
parent[ulp_v] = ulp_u;
// Update the size
size[ulp_u] += size[ulp_v];
}
}
}
public class Main {
public static void main(String[] args) {
// Disjoint Data structure
DisjointSet ds = new DisjointSet(7);
ds.unionByRank(1, 2); // Adding edge between 1 and 2
ds.unionByRank(2, 3); // Adding edge between 2 and 3
ds.unionByRank(4, 5); // Adding edge between 4 and 5
ds.unionByRank(6, 7); // Adding edge between 6 and 7
ds.unionByRank(5, 6); // Adding edge between 5 and 6
/* Checking if node 3 and node 7
are in the same component */
if (ds.find(3, 7))
System.out.println("They belong to the same components.");
else
System.out.println("They do not belong to the same components.");
ds.unionByRank(3, 7); // Adding edge between 3 and 7
/* Checking again if node 3 and node 7
are in the same component */
if (ds.find(3, 7))
System.out.println("They belong to the same components.");
else
System.out.println("They do not belong to the same components.");
}
}
class DisjointSet:
# Constructor
def __init__(self, n):
# Resize the arrays
self.rank = [0] * (n + 1)
self.parent = [i for i in range(n + 1)]
self.size = [1] * (n + 1)
# Helper function to find ultimate
# parent along with path compression
def findUPar(self, node):
# Base case
if node == self.parent[node]:
return node
# Backtracking step for path compression
self.parent[node] = self.findUPar(self.parent[node])
return self.parent[node]
# Function to determine if two nodes
# are in the same component or not
def find(self, u, v):
# Return true if both have same ultimate parent
return self.findUPar(u) == self.findUPar(v)
# Function to perform union of
# two nodes based on ranks
def unionByRank(self, u, v):
# Get the ultimate parents of both nodes
ulp_u = self.findUPar(u)
ulp_v = self.findUPar(v)
# Return if nodes already belong to the same component
if ulp_u == ulp_v:
return
# Otherwise, join the node to the other
# node having higher ranks among the two
if self.rank[ulp_u] < self.rank[ulp_v]:
# Update the parent
self.parent[ulp_u] = ulp_v
elif self.rank[ulp_v] < self.rank[ulp_u]:
# Update the parent
self.parent[ulp_v] = ulp_u
else:
# Update the parent
self.parent[ulp_v] = ulp_u
# Update the rank
self.rank[ulp_u] += 1
# Function to perform union of
# two nodes based on sizes
def unionBySize(self, u, v):
# Get the ultimate parents of both nodes
ulp_u = self.findUPar(u)
ulp_v = self.findUPar(v)
# Return if nodes already belong to the same component
if ulp_u == ulp_v:
return
# Otherwise, join the node belonging to the smaller
# component to the node belonging to bigger component
if self.size[ulp_u] < self.size[ulp_v]:
# Update the parent
self.parent[ulp_u] = ulp_v
# Update the size
self.size[ulp_v] += self.size[ulp_u]
else:
# Update the parent
self.parent[ulp_v] = ulp_u
# Update the size
self.size[ulp_u] += self.size[ulp_v]
if __name__ == "__main__":
# Disjoint Data structure
ds = DisjointSet(7)
ds.unionByRank(1, 2) # Adding edge between 1 and 2
ds.unionByRank(2, 3) # Adding edge between 2 and 3
ds.unionByRank(4, 5) # Adding edge between 4 and 5
ds.unionByRank(6, 7) # Adding edge between 6 and 7
ds.unionByRank(5, 6) # Adding edge between 5 and 6
# Checking if node 3 and node 7
# are in the same component
if ds.find(3, 7):
print("They belong to the same components.")
else:
print("They do not belong to the same components.")
ds.unionByRank(3, 7) # Adding edge between 3 and 7
# Checking again if node 3 and node 7
# are in the same component
if ds.find(3, 7):
print("They belong to the same components.")
else:
print("They do not belong to the same components.")
class DisjointSet {
// Constructor
constructor(n) {
// Resize the arrays
this.rank = new Array(n + 1).fill(0);
this.parent = new Array(n + 1);
this.size = new Array(n + 1).fill(1);
// Initialise each node with its own value
for (let i = 0; i <= n; i++) {
this.parent[i] = i;
}
}
/* Helper function to find ultimate
parent along with path compression */
findUPar(node) {
// Base case
if (node === this.parent[node])
return node;
// Backtracking step for path compression
this.parent[node] = this.findUPar(this.parent[node]);
return this.parent[node];
}
/* Function to determine if two nodes
are in the same component or not */
find(u, v) {
// Return true if both have same ultimate parent
return (this.findUPar(u) === this.findUPar(v));
}
/* Function to perform union of
two nodes based on ranks */
unionByRank(u, v) {
// Get the ultimate parents of both nodes
let ulp_u = this.findUPar(u);
let ulp_v = this.findUPar(v);
// Return if nodes already belong to the same component
if (ulp_u === ulp_v) return;
/* Otherwise, join the node to the other
node having higher ranks among the two */
if (this.rank[ulp_u] < this.rank[ulp_v]) {
// Update the parent
this.parent[ulp_u] = ulp_v;
} else if (this.rank[ulp_v] < this.rank[ulp_u]) {
// Update the parent
this.parent[ulp_v] = ulp_u;
}
/* If both have same rank, join any node to the
other and increment the rank of the parent node */
else {
// Update the parent
this.parent[ulp_v] = ulp_u;
// Update the rank
this.rank[ulp_u]++;
}
}
/* Function to perform union of
two nodes based on sizes */
unionBySize(u, v) {
// Get the ultimate parents of both nodes
let ulp_u = this.findUPar(u);
let ulp_v = this.findUPar(v);
// Return if nodes already belong to the same component
if (ulp_u === ulp_v) return;
/* Otherwise, join the node belonging to the smaller
component to the node belonging to bigger component */
if (this.size[ulp_u] < this.size[ulp_v]) {
// Update the parent
this.parent[ulp_u] = ulp_v;
// Update the size
this.size[ulp_v] += this.size[ulp_u];
} else {
// Update the parent
this.parent[ulp_v] = ulp_u;
// Update the size
this.size[ulp_u] += this.size[ulp_v];
}
}
}
const main = () => {
// Disjoint Data structure
const ds = new DisjointSet(7);
ds.unionByRank(1, 2); // Adding edge between 1 and 2
ds.unionByRank(2, 3); // Adding edge between 2 and 3
ds.unionByRank(4, 5); // Adding edge between 4 and 5
ds.unionByRank(6, 7); // Adding edge between 6 and 7
ds.unionByRank(5, 6); // Adding edge between 5 and 6
/* Checking if node 3 and node 7
are in the same component */
if (ds.find(3, 7))
console.log("They belong to the same components.");
else
console.log("They do not belong to the same components.");
ds.unionByRank(3, 7); // Adding edge between 3 and 7
/* Checking again if node 3 and node 7
are in the same component */
if (ds.find(3, 7))
console.log("They belong to the same components.");
else
console.log("They do not belong to the same components.");
};
main();
Time Complexity: O(1)
The actual time complexity of UnionByRank() and findPar() methods is O(4α), which is very small and close to 1. This 4α term has a long mathematical derivation not required for an interview.
Space Complexity: O(2N) (where N is the number of nodes)
The Disjoint Set Data structure uses a parent and a rank array each of size N.
The Disjoint Set data structure is a crucial topic in the graph series. To understand its necessity, consider the following problem:
{{1,2},{2,3},{4,5},{6,7},{5,6},{3,7}}
The Disjoint Set data structure provides two primary functionalities:
To implement Union by Size, two arrays of size N (number of nodes) are needed:
In the Union by Rank function, the actual ranks were getting distored during the process of path compression. However, for the case of Union by Size function, there is no distortion of sizes during the process of path compression.
#include <bits/stdc++.h>
using namespace std;
class DisjointSet {
private:
// To store the rank of each node
vector<int> rank;
/* To store the size of components
that each node belongs to */
vector<int> size;
// To store the ultimate parent of each node
vector<int> parent;
public:
// Constructor
DisjointSet(int n) {
// Resize the arrays
rank.resize(n + 1, 0);
parent.resize(n + 1);
size.resize(n + 1, 1);
// Initialise each node with its own value
for (int i = 0; i <= n; i++) {
parent[i] = i;
}
}
/* Helper function to find ultimate
parent along with path compression */
int findUPar(int node) {
// Base case
if (node == parent[node])
return node;
// Backtracking step for path compression
return parent[node] = findUPar(parent[node]);
}
/* Function to detemine if two nodes
are in the same component or not */
bool find(int u, int v) {
// Return true if both have same ultimate parent
return (findUPar(u) == findUPar(v));
}
/* Function to perform union of
two nodes based on ranks */
void unionByRank(int u, int v) {
// Get the ultimate parents of both nodes
int ulp_u = findUPar(u);
int ulp_v = findUPar(v);
// Return if nodes already belong to the same component
if (ulp_u == ulp_v) return;
/* Otherwise, join the node to the other
node having higher ranks among the two */
if (rank[ulp_u] < rank[ulp_v]) {
// Update the parent
parent[ulp_u] = ulp_v;
}
else if (rank[ulp_v] < rank[ulp_u]) {
// Update the parent
parent[ulp_v] = ulp_u;
}
/* If both have same rank, join any node to the
other and increment the rank of the parent node */
else {
// Update the parent
parent[ulp_v] = ulp_u;
// Update the rank
rank[ulp_u]++;
}
}
/* Function to perform union of
two nodes based on ranks */
void unionBySize(int u, int v) {
// Get the ultimate parents of both nodes
int ulp_u = findUPar(u);
int ulp_v = findUPar(v);
// Return if nodes already belong to the same component
if (ulp_u == ulp_v) return;
/* Otherwise, join the node belonging to the smaller
component to the node belonging to bigger component */
if (size[ulp_u] < size[ulp_v]) {
// Update the parent
parent[ulp_u] = ulp_v;
// Update the size
size[ulp_v] += size[ulp_u];
}
else {
// Update the parent
parent[ulp_v] = ulp_u;
// Update the size
size[ulp_u] += size[ulp_v];
}
}
};
int main() {
// Disjoint Data structure
DisjointSet ds(7);
ds.unionBySize(1, 2); // Adding edge between 1 and 2
ds.unionBySize(2, 3); // Adding edge between 2 and 3
ds.unionBySize(4, 5); // Adding edge between 4 and 5
ds.unionBySize(6, 7); // Adding edge between 6 and 7
ds.unionBySize(5, 6); // Adding edge between 5 and 6
/* Checking if node 3 and node 7
are in the same component */
if (ds.find(3, 7))
cout << "They belong to the same components.\n";
else
cout << "They do not belong to the same components.\n";
ds.unionBySize(3, 7); // Adding edge between 3 and 7
/* Checking again if node 3 and node 7
are in the same component */
if (ds.find(3, 7))
cout << "They belong to the same components.\n";
else
cout << "They do not belong to the same components.\n";
return 0;
}
import java.util.*;
class DisjointSet {
// To store the rank of each node
private int[] rank;
/* To store the size of components
that each node belongs to */
private int[] size;
// To store the ultimate parent of each node
private int[] parent;
// Constructor
public DisjointSet(int n) {
// Resize the arrays
rank = new int[n + 1];
parent = new int[n + 1];
size = new int[n + 1];
Arrays.fill(size, 1);
// Initialise each node with its own value
for (int i = 0; i <= n; i++) {
parent[i] = i;
}
}
/* Helper function to find ultimate
parent along with path compression */
public int findUPar(int node) {
// Base case
if (node == parent[node])
return node;
// Backtracking step for path compression
return parent[node] = findUPar(parent[node]);
}
/* Function to determine if two nodes
are in the same component or not */
public boolean find(int u, int v) {
// Return true if both have same ultimate parent
return (findUPar(u) == findUPar(v));
}
/* Function to perform union of
two nodes based on ranks */
public void unionByRank(int u, int v) {
// Get the ultimate parents of both nodes
int ulp_u = findUPar(u);
int ulp_v = findUPar(v);
// Return if nodes already belong to the same component
if (ulp_u == ulp_v) return;
/* Otherwise, join the node to the other
node having higher ranks among the two */
if (rank[ulp_u] < rank[ulp_v]) {
// Update the parent
parent[ulp_u] = ulp_v;
} else if (rank[ulp_v] < rank[ulp_u]) {
// Update the parent
parent[ulp_v] = ulp_u;
}
/* If both have same rank, join any node to the
other and increment the rank of the parent node */
else {
// Update the parent
parent[ulp_v] = ulp_u;
// Update the rank
rank[ulp_u]++;
}
}
/* Function to perform union of
two nodes based on sizes */
public void unionBySize(int u, int v) {
// Get the ultimate parents of both nodes
int ulp_u = findUPar(u);
int ulp_v = findUPar(v);
// Return if nodes already belong to the same component
if (ulp_u == ulp_v) return;
/* Otherwise, join the node belonging to the smaller
component to the node belonging to bigger component */
if (size[ulp_u] < size[ulp_v]) {
// Update the parent
parent[ulp_u] = ulp_v;
// Update the size
size[ulp_v] += size[ulp_u];
} else {
// Update the parent
parent[ulp_v] = ulp_u;
// Update the size
size[ulp_u] += size[ulp_v];
}
}
}
public class Main {
public static void main(String[] args) {
// Disjoint Data structure
DisjointSet ds = new DisjointSet(7);
ds.unionBySize(1, 2); // Adding edge between 1 and 2
ds.unionBySize(2, 3); // Adding edge between 2 and 3
ds.unionBySize(4, 5); // Adding edge between 4 and 5
ds.unionBySize(6, 7); // Adding edge between 6 and 7
ds.unionBySize(5, 6); // Adding edge between 5 and 6
/* Checking if node 3 and node 7
are in the same component */
if (ds.find(3, 7))
System.out.println("They belong to the same components.");
else
System.out.println("They do not belong to the same components.");
ds.unionBySize(3, 7); // Adding edge between 3 and 7
/* Checking again if node 3 and node 7
are in the same component */
if (ds.find(3, 7))
System.out.println("They belong to the same components.");
else
System.out.println("They do not belong to the same components.");
}
}
class DisjointSet:
# Constructor
def __init__(self, n):
# Resize the arrays
self.rank = [0] * (n + 1)
self.parent = [i for i in range(n + 1)]
self.size = [1] * (n + 1)
# Helper function to find ultimate
# parent along with path compression
def findUPar(self, node):
# Base case
if node == self.parent[node]:
return node
# Backtracking step for path compression
self.parent[node] = self.findUPar(self.parent[node])
return self.parent[node]
# Function to determine if two nodes
# are in the same component or not
def find(self, u, v):
# Return true if both have same ultimate parent
return self.findUPar(u) == self.findUPar(v)
# Function to perform union of
# two nodes based on ranks
def unionByRank(self, u, v):
# Get the ultimate parents of both nodes
ulp_u = self.findUPar(u)
ulp_v = self.findUPar(v)
# Return if nodes already belong to the same component
if ulp_u == ulp_v:
return
# Otherwise, join the node to the other
# node having higher ranks among the two
if self.rank[ulp_u] < self.rank[ulp_v]:
# Update the parent
self.parent[ulp_u] = ulp_v
elif self.rank[ulp_v] < self.rank[ulp_u]:
# Update the parent
self.parent[ulp_v] = ulp_u
else:
# Update the parent
self.parent[ulp_v] = ulp_u
# Update the rank
self.rank[ulp_u] += 1
# Function to perform union of
# two nodes based on sizes
def unionBySize(self, u, v):
# Get the ultimate parents of both nodes
ulp_u = self.findUPar(u)
ulp_v = self.findUPar(v)
# Return if nodes already belong to the same component
if ulp_u == ulp_v:
return
# Otherwise, join the node belonging to the smaller
# component to the node belonging to bigger component
if self.size[ulp_u] < self.size[ulp_v]:
# Update the parent
self.parent[ulp_u] = ulp_v
# Update the size
self.size[ulp_v] += self.size[ulp_u]
else:
# Update the parent
self.parent[ulp_v] = ulp_u
# Update the size
self.size[ulp_u] += self.size[ulp_v]
if __name__ == "__main__":
# Disjoint Data structure
ds = DisjointSet(7)
ds.unionBySize(1, 2) # Adding edge between 1 and 2
ds.unionBySize(2, 3) # Adding edge between 2 and 3
ds.unionBySize(4, 5) # Adding edge between 4 and 5
ds.unionBySize(6, 7) # Adding edge between 6 and 7
ds.unionBySize(5, 6) # Adding edge between 5 and 6
# Checking if node 3 and node 7
# are in the same component
if ds.find(3, 7):
print("They belong to the same components.")
else:
print("They do not belong to the same components.")
ds.unionBySize(3, 7) # Adding edge between 3 and 7
# Checking again if node 3 and node 7
# are in the same component
if ds.find(3, 7):
print("They belong to the same components.")
else:
print("They do not belong to the same components.")
class DisjointSet {
// Constructor
constructor(n) {
// Resize the arrays
this.rank = new Array(n + 1).fill(0);
this.parent = new Array(n + 1);
this.size = new Array(n + 1).fill(1);
// Initialise each node with its own value
for (let i = 0; i <= n; i++) {
this.parent[i] = i;
}
}
/* Helper function to find ultimate
parent along with path compression */
findUPar(node) {
// Base case
if (node === this.parent[node])
return node;
// Backtracking step for path compression
this.parent[node] = this.findUPar(this.parent[node]);
return this.parent[node];
}
/* Function to determine if two nodes
are in the same component or not */
find(u, v) {
// Return true if both have same ultimate parent
return (this.findUPar(u) === this.findUPar(v));
}
/* Function to perform union of
two nodes based on ranks */
unionByRank(u, v) {
// Get the ultimate parents of both nodes
let ulp_u = this.findUPar(u);
let ulp_v = this.findUPar(v);
// Return if nodes already belong to the same component
if (ulp_u === ulp_v) return;
/* Otherwise, join the node to the other
node having higher ranks among the two */
if (this.rank[ulp_u] < this.rank[ulp_v]) {
// Update the parent
this.parent[ulp_u] = ulp_v;
} else if (this.rank[ulp_v] < this.rank[ulp_u]) {
// Update the parent
this.parent[ulp_v] = ulp_u;
}
/* If both have same rank, join any node to the
other and increment the rank of the parent node */
else {
// Update the parent
this.parent[ulp_v] = ulp_u;
// Update the rank
this.rank[ulp_u]++;
}
}
/* Function to perform union of
two nodes based on sizes */
unionBySize(u, v) {
// Get the ultimate parents of both nodes
let ulp_u = this.findUPar(u);
let ulp_v = this.findUPar(v);
// Return if nodes already belong to the same component
if (ulp_u === ulp_v) return;
/* Otherwise, join the node belonging to the smaller
component to the node belonging to bigger component */
if (this.size[ulp_u] < this.size[ulp_v]) {
// Update the parent
this.parent[ulp_u] = ulp_v;
// Update the size
this.size[ulp_v] += this.size[ulp_u];
} else {
// Update the parent
this.parent[ulp_v] = ulp_u;
// Update the size
this.size[ulp_u] += this.size[ulp_v];
}
}
}
const main = () => {
// Disjoint Data structure
const ds = new DisjointSet(7);
ds.unionBySize(1, 2); // Adding edge between 1 and 2
ds.unionBySize(2, 3); // Adding edge between 2 and 3
ds.unionBySize(4, 5); // Adding edge between 4 and 5
ds.unionBySize(6, 7); // Adding edge between 6 and 7
ds.unionBySize(5, 6); // Adding edge between 5 and 6
/* Checking if node 3 and node 7
are in the same component */
if (ds.find(3, 7))
console.log("They belong to the same components.");
else
console.log("They do not belong to the same components.");
ds.unionBySize(3, 7); // Adding edge between 3 and 7
/* Checking again if node 3 and node 7
are in the same component */
if (ds.find(3, 7))
console.log("They belong to the same components.");
else
console.log("They do not belong to the same components.");
};
main();
Time Complexity: O(1)
The actual time complexity of UnionBySize() and findPar() methods is O(4α), which is very small and close to 1. This 4α term has a long mathematical derivation not required for an interview.
Space Complexity: O(2N) (where N is the number of nodes)
The Disjoint Set Data structure uses a parent and a size array each of size N.
Q: Why does Union-Find need Path Compression?
A: Without it, finding the root takes O(n) in the worst case. Path compression flattens the structure, making future lookups nearly O(1).
Q: Can we use Union by Rank and Union by Size together?
A: No, we should only use one heuristic at a time. Using both would reduce effectiveness because size-based merging does not directly track tree height.
Q: How would this change if nodes were added dynamically?
A: Extend parent[] and rank[]/size[] dynamically using a hash map instead of fixed arrays.
Q: What if we wanted to track the actual connected components?
A: Maintain a list of elements in each component and update it during union().