Introduction to Disjoint Set (UnionFind Algorithm) (original) (raw)

Try it on GfG Practice redirect icon

Two sets are called **disjoint sets if they don't have any element in common. The disjoint set data structure is used to store such sets. It supports following operations:

Consider a situation with a number of persons and the following tasks to be performed on them:

**Examples:

We are given 10 individuals say, a, b, c, d, e, f, g, h, i, j

Following are relationships to be added:
a <-> b
b <-> d
c <-> f
c <-> i
j <-> e
g <-> j

Given queries like whether a is a friend of d or not. We basically need to create following 4 groups and maintain a quickly accessible connection among group items:
G1 = {a, b, d}
G2 = {c, f, i}
G3 = {e, g, j}
G4 = {h}

Find whether x and y belong to the same group or not, i.e. to find if x and y are direct/indirect friends.

Partitioning the individuals into different sets according to the groups in which they fall. This method is known as a **Disjoint set Union which maintains a collection of **Disjoint sets and each set is represented by one of its members.

**To answer the above question two key points to be considered are:

**Data Structures used are:

**Array: An array of integers is called **Parent[]. If we are dealing with **N items, i'th element of the array represents the i'th item. More precisely, the i'th element of the Parent[] array is the parent of the i'th item. These relationships create one or more virtual trees.

**Tree: It is a **Disjoint set. If two elements are in the same tree, then they are in the same **Disjoint set. The root node (or the topmost node) of each tree is called the **representative of the set. There is always a single **unique representative of each set. A simple rule to identify a representative is if 'i' is the representative of a set, then **Parent[i] = i. If i is not the representative of his set, then it can be found by traveling up the tree until we find the representative.

**Operations on Disjoint Set Data Structures:

**1. Find:

The task is to find representative of the set of a given element. The representative is always root of the tree. So we implement find() by recursively traversing the parent array until we hit a node that is root (parent of itself).

**2. Union:

The task is to combine two sets and make one. It takes **two elements as input and finds the representatives of their sets using the **Find operation, and finally puts either one of the trees (representing the set) under the root node of the other tree.

C++ `

#include #include using namespace std;

class UnionFind { vector parent; public: UnionFind(int size) {

    parent.resize(size);
  
    // Initialize the parent array with each 
    // element as its own representative
    for (int i = 0; i < size; i++) {
        parent[i] = i;
    }
}

// Find the representative (root) of the
// set that includes element i
int find(int i) {
  
    // If i itself is root or representative
    if (parent[i] == i) {
        return i;
    }
  
    // Else recursively find the representative 
    // of the parent
    return find(parent[i]);
}

// Unite (merge) the set that includes element 
// i and the set that includes element j
void unite(int i, int j) {
  
    // Representative of set containing i
    int irep = find(i);
  
    // Representative of set containing j
    int jrep = find(j);
   
    // Make the representative of i's set
    // be the representative of j's set
    parent[irep] = jrep;
}

};

int main() { int size = 5; UnionFind uf(size); uf.unite(1, 2); uf.unite(3, 4); bool inSameSet = (uf.find(1) == uf.find(2)); cout << "Are 1 and 2 in the same set? " << (inSameSet ? "Yes" : "No") << endl; return 0; }

Java

import java.util.Arrays;

public class UnionFind { private int[] parent;

public UnionFind(int size) {
  
    // Initialize the parent array with each 
    // element as its own representative
    parent = new int[size];
    for (int i = 0; i < size; i++) {
        parent[i] = i;
    }
}

// Find the representative (root) of the 
// set that includes element i
public int find(int i) {
  
    // if i itself is root or representative
    if (parent[i] == i) {
        return i;
    }
  
    // Else recursively find the representative
    // of the parent 
    return find(parent[i]);
}

// Unite (merge) the set that includes element 
// i and the set that includes element j
public void union(int i, int j) {
  
    // Representative of set containing i
    int irep = find(i);

    // Representative of set containing j
    int jrep = find(j);

    // Make the representative of i's set be 
    // the representative of j's set
    parent[irep] = jrep;
}

public static void main(String[] args) {
    int size = 5;
    UnionFind uf = new UnionFind(size);
    uf.union(1, 2);
    uf.union(3, 4);
    boolean inSameSet = uf.find(1) == uf.find(2);
    System.out.println("Are 1 and 2 in the same set? " + inSameSet);
}

}

Python

class UnionFind: def init(self, size):

    # Initialize the parent array with each 
    # element as its own representative
    self.parent = list(range(size))

def find(self, i):
  
    # If i itself is root or representative
    if self.parent[i] == i:
        return i
      
    # Else recursively find the representative 
    # of the parent
    return self.find(self.parent[i])

def unite(self, i, j):
  
    # Representative of set containing i
    irep = self.find(i)
    
    # Representative of set containing j
    jrep = self.find(j)
    
    # Make the representative of i's set
    # be the representative of j's set
    self.parent[irep] = jrep

Example usage

size = 5 uf = UnionFind(size) uf.unite(1, 2) uf.unite(3, 4) in_same_set = (uf.find(1) == uf.find(2)) print("Are 1 and 2 in the same set?", "Yes" if in_same_set else "No")

C#

using System;

public class UnionFind { private int[] parent;

public UnionFind(int size) {
  
    // Initialize the parent array with each 
    // element as its own representative
    parent = new int[size];
    for (int i = 0; i < size; i++) {
        parent[i] = i;
    }
}

// Find the representative (root) of the 
// set that includes element i
public int Find(int i) {
  
    // If i itself is root or representative
    if (parent[i] == i) {
        return i;
    }
    // Else recursively find the representative 
    // of the parent
    return Find(parent[i]);
}

// Unite (merge) the set that includes element 
// i and the set that includes element j
public void Union(int i, int j) {
  
    // Representative of set containing i
    int irep = Find(i);

    // Representative of set containing j
    int jrep = Find(j);

    // Make the representative of i's set be 
    // the representative of j's set
    parent[irep] = jrep;
}

public static void Main(string[] args) {
    int size = 5;
    UnionFind uf = new UnionFind(size);
    uf.Union(1, 2);
    uf.Union(3, 4);
    bool inSameSet = uf.Find(1) == uf.Find(2);
    Console.WriteLine("Are 1 and 2 in the same set? " + 
                      (inSameSet ? "Yes" : "No"));
}

}

JavaScript

class UnionFind { constructor(size) {

    // Initialize the parent array with each 
    // element as its own representative
    this.parent = Array.from({ length: size }, (_, i) => i);
}

find(i) {

    // If i itself is root or representative
    if (this.parent[i] === i) {
        return i;
    }
    
    // Else recursively find the representative 
    // of the parent
    return this.find(this.parent[i]);
}

unite(i, j) {

    // Representative of set containing i
    const irep = this.find(i);
    
    // Representative of set containing j
    const jrep = this.find(j);
    
    // Make the representative of i's set
    // be the representative of j's set
    this.parent[irep] = jrep;
}

}

// Example usage const size = 5; const uf = new UnionFind(size);

// Unite sets containing 1 and 2, and 3 and 4 uf.unite(1, 2); uf.unite(3, 4);

// Check if 1 and 2 are in the same set const inSameSet = uf.find(1) === uf.find(2); console.log("Are 1 and 2 in the same set?", inSameSet ? "Yes" : "No");

`

Output

Are 1 and 2 in the same set? Yes

The above _union() and _find() are naive and the worst case time complexity is linear. The trees created to represent subsets can be skewed and can become like a linked list. Following is an example of worst case scenario.

**Optimization (Path Compression and Union by Rank/Size):

The main idea is to reduce heights of trees representing different sets. We achieve this with two most common methods:

  1. Path Compression
  2. Union By Rank

**Path Compression (Used to improve find()):

The idea is to flatten the tree when _find() is called. When find() is called for an element x, root of the tree is returned. The _find() operation traverses up from x to find root. The idea of path compression is to make the found root as parent of x so that we don’t have to traverse all intermediate nodes again. If x is root of a subtree, then path (to root) from all nodes under x also compresses.

It speeds up the data structure by compressing the height of the trees. It can be achieved by inserting a small caching mechanism into the **Find operation. Take a look at the code for more details:

**Union by Rank (Modifications to union())

Rank is like height of the trees representing different sets.We use an extraarray of integers called **rank[]. The size of this array is the same as the parent array **Parent[]. If i is a representative of a set, rank[i] is the rank of the element i. Rank is same as height if path compression is not used. With path compression, rank can be more than the actual height.
Now recall that in the Union operation, it doesn’t matter which of the two trees is moved under the other. Now what we want to do is minimize the height of the resulting tree. If we are uniting two trees (or sets), let’s call them left and right, then it all depends on the rank of left and the rank of right.

#include #include using namespace std;

class DisjointUnionSets { vector rank, parent;

public:

// Constructor to initialize sets
DisjointUnionSets(int n) {
    rank.resize(n, 0);
    parent.resize(n);

    // Initially, each element is in its own set
    for (int i = 0; i < n; i++) {
        parent[i] = i;
    }
}

// Find the representative of the set that x belongs to
int find(int i) {
    int root = parent[i];
  
    if (parent[root] != root) {
        return parent[i] = find(root);
    }
  
    return root;
}

// Union of sets containing x and y
void unionSets(int x, int y) {
    int xRoot = find(x);
    int yRoot = find(y);

    // If they are in the same set, no need to union
    if (xRoot == yRoot) return;

    // Union by rank
    if (rank[xRoot] < rank[yRoot]) {
        parent[xRoot] = yRoot;
    } else if (rank[yRoot] < rank[xRoot]) {
        parent[yRoot] = xRoot;
    } else {
        parent[yRoot] = xRoot;
        rank[xRoot]++;
    }
}

};

int main() { // Let there be 5 persons with ids 0, 1, 2, 3, and 4 int n = 5; DisjointUnionSets dus(n);

// 0 is a friend of 2
dus.unionSets(0, 2);

// 4 is a friend of 2
dus.unionSets(4, 2);

// 3 is a friend of 1
dus.unionSets(3, 1);

// Check if 4 is a friend of 0
if (dus.find(4) == dus.find(0))
    cout << "Yes\n";
else
    cout << "No\n";

// Check if 1 is a friend of 0
if (dus.find(1) == dus.find(0))
    cout << "Yes\n";
else
    cout << "No\n";

return 0;

}

Java

// A Java program to implement Disjoint Set with // Path Compression and Union by Rank import java.io.; import java.util.;

class DisjointUnionSets { int[] rank, parent; int n;

// Constructor
public DisjointUnionSets(int n)
{
    rank = new int[n];
    parent = new int[n];
    this.n = n;
    for (int i = 0; i < n; i++) {
        // Initially, all elements are in
        // their own set.
        parent[i] = i;
    }
}

// Returns representative of x's set
public int find(int i) {
    
    int root = parent[i];
  
    if (parent[root] != root) {
        return parent[i] = find(root);
    }
  
    return root;
}

// Unites the set that includes x and the set
// that includes x
void union(int x, int y)
{
    // Find representatives of two sets
    int xRoot = find(x), yRoot = find(y);

    // Elements are in the same set, no need
    // to unite anything.
    if (xRoot == yRoot)
        return;

    // If x's rank is less than y's rank
    if (rank[xRoot] < rank[yRoot])

        // Then move x under y  so that depth
        // of tree remains less
        parent[xRoot] = yRoot;

    // Else if y's rank is less than x's rank
    else if (rank[yRoot] < rank[xRoot])

        // Then move y under x so that depth of
        // tree remains less
        parent[yRoot] = xRoot;

    else // if ranks are the same
    {
        // Then move y under x (doesn't matter
        // which one goes where)
        parent[yRoot] = xRoot;

        // And increment the result tree's
        // rank by 1
        rank[xRoot] = rank[xRoot] + 1;
    }
}

}

// Driver code public class Main { public static void main(String[] args) { // Let there be 5 persons with ids as // 0, 1, 2, 3 and 4 int n = 5; DisjointUnionSets dus = new DisjointUnionSets(n);

    // 0 is a friend of 2
    dus.union(0, 2);

    // 4 is a friend of 2
    dus.union(4, 2);

    // 3 is a friend of 1
    dus.union(3, 1);

    // Check if 4 is a friend of 0
    if (dus.find(4) == dus.find(0))
        System.out.println("Yes");
    else
        System.out.println("No");

    // Check if 1 is a friend of 0
    if (dus.find(1) == dus.find(0))
        System.out.println("Yes");
    else
        System.out.println("No");
}

}

Python

class DisjointUnionSets: def init(self, n): self.rank = [0] * n self.parent = list(range(n))

def find(self, i):
    
    root = self.parent[i]
  
    if self.parent[root] != root:
        self.parent[i] = self.find(root)
        return self.parent[i]
  
    return root

def unionSets(self, x, y):
    xRoot = self.find(x)
    yRoot = self.find(y)

    if xRoot == yRoot:
        return

    # Union by Rank   
    if self.rank[xRoot] < self.rank[yRoot]:
        self.parent[xRoot] = yRoot
    elif self.rank[yRoot] < self.rank[xRoot]:
        self.parent[yRoot] = xRoot
    else:
        self.parent[yRoot] = xRoot
        self.rank[xRoot] += 1

if name == 'main':

n = 5  # Let there be 5 persons with ids 0, 1, 2, 3, and 4
dus = DisjointUnionSets(n)

dus.unionSets(0, 2)  # 0 is a friend of 2
dus.unionSets(4, 2)  # 4 is a friend of 2
dus.unionSets(3, 1)  # 3 is a friend of 1

# Check if 4 is a friend of 0
if dus.find(4) == dus.find(0):
    print('Yes')
else:
    print('No')

# Check if 1 is a friend of 0
if dus.find(1) == dus.find(0):
    print('Yes')
else:
    print('No')

C#

// A C# program to implement Disjoint Set with // Path Compression and Union by Rank using System;

class DisjointUnionSets { int[] rank, parent; int n;

// Constructor
public DisjointUnionSets(int n)
{
    rank = new int[n];
    parent = new int[n];
    this.n = n;
    for (int i = 0; i < n; i++) {
      
        // Initially, all elements are in
        // their own set.
        parent[i] = i;
    }
}

// Returns representative of x's set
public int find(int i) {
  
    int root = parent[i];
  
    if (parent[root] != root) {
        return parent[i] = find(root);
    }
  
    return root;
}

// Unites the set that includes x and the set
// that includes y
public void union(int x, int y)
{
    // Find representatives of two sets
    int xRoot = find(x), yRoot = find(y);

    // Elements are in the same set, no need
    // to unite anything.
    if (xRoot == yRoot)
        return;

    // If x's rank is less than y's rank
    if (rank[xRoot] < rank[yRoot])
        // Then move x under y  so that depth
        // of tree remains less
        parent[xRoot] = yRoot;
    // Else if y's rank is less than x's rank
    else if (rank[yRoot] < rank[xRoot])
        // Then move y under x so that depth of
        // tree remains less
        parent[yRoot] = xRoot;
    else // if ranks are the same
    {
        // Then move y under x (doesn't matter
        // which one goes where)
        parent[yRoot] = xRoot;
        // And increment the result tree's
        // rank by 1
        rank[xRoot] = rank[xRoot] + 1;
    }
}

}

// Driver code class Program { static void Main(string[] args) { // Let there be 5 persons with ids as // 0, 1, 2, 3 and 4 int n = 5; DisjointUnionSets dus = new DisjointUnionSets(n);

    // 0 is a friend of 2
    dus.union(0, 2);

    // 4 is a friend of 2
    dus.union(4, 2);

    // 3 is a friend of 1
    dus.union(3, 1);

    // Check if 4 is a friend of 0
    if (dus.find(4) == dus.find(0))
        Console.WriteLine("Yes");
    else
        Console.WriteLine("No");

    // Check if 1 is a friend of 0
    if (dus.find(1) == dus.find(0))
        Console.WriteLine("Yes");
    else
        Console.WriteLine("No");
}

}

JavaScript

class DisjointUnionSets { constructor(n) { this.rank = new Array(n).fill(0); this.parent = Array.from({length: n}, (_, i) => i);

    // Initially, each element is in its own set
}

find(i) {
    
    let root = this.parent[i];

    if (this.parent[root] !== root) {
        return this.parent[i] = this.find(root);
    }
    
    return root;
}

unionSets(x, y) {
    const xRoot = this.find(x);
    const yRoot = this.find(y);

    // If they are in the same set, no need to union
    if (xRoot === yRoot) return;

    // Union by rank
    if (this.rank[xRoot] < this.rank[yRoot]) {
        this.parent[xRoot] = yRoot;
    } else if (this.rank[yRoot] < this.rank[xRoot]) {
        this.parent[yRoot] = xRoot;
    } else {
        this.parent[yRoot] = xRoot;
        this.rank[xRoot]++;
    }
}

}

const n = 5; // Let there be 5 persons with ids 0, 1, 2, 3, and 4 const dus = new DisjointUnionSets(n);

// 0 is a friend of 2 dus.unionSets(0, 2); // 4 is a friend of 2 dus.unionSets(4, 2); // 3 is a friend of 1 dus.unionSets(3, 1);

// Check if 4 is a friend of 0 if (dus.find(4) === dus.find(0)) console.log('Yes'); else console.log('No');

// Check if 1 is a friend of 0 if (dus.find(1) === dus.find(0)) console.log('Yes'); else console.log('No');

`

**Time complexity: O(n) for creating n single item sets . The two techniques -path compression with the union by rank/size, the time complexity will reach nearly constant time. It turns out, that the final amortized time complexity is O(α(n)), where α(n) is the inverse Ackermann function, which grows very steadily (it does not even exceed for n<10600 approximately).

**Space complexity: O(n) because we need to store n elements in the Disjoint Set Data Structure.

**Union by Size (Alternate of Union by Rank)

We use an array of integers called **size[]. The size of this array is the same as the parent array **Parent[]. If i is a representative of a set, size[i] is the number of the elements in the tree representing the set.
Now we are uniting two trees (or sets), let’s call them left and right, then in this case it all depends on the size of left and the size of right tree (or set).

// C++ program for Union by Size with Path Compression #include #include using namespace std;

class UnionFind { vector Parent; vector Size; public: UnionFind(int n) {
Parent.resize(n); for (int i = 0; i < n; i++) { Parent[i] = i; }

    // Initialize Size array with 1s
    Size.resize(n, 1);
}

// Function to find the representative (or the root
// node) for the set that includes i
int find(int i) {
    int root = Parent[i];
  
    if (Parent[root] != root) {
        return Parent[i] = find(root);
    }
  
    return root;
}

// Unites the set that includes i and the set that
// includes j by size
void unionBySize(int i, int j) {
  
    // Find the representatives (or the root nodes) for
    // the set that includes i
    int irep = find(i);

    // And do the same for the set that includes j
    int jrep = find(j);

    // Elements are in the same set, no need to unite
    // anything.
    if (irep == jrep)
        return;

    // Get the size of i’s tree
    int isize = Size[irep];

    // Get the size of j’s tree
    int jsize = Size[jrep];

    // If i’s size is less than j’s size
    if (isize < jsize) {
      
        // Then move i under j
        Parent[irep] = jrep;

        // Increment j's size by i's size
        Size[jrep] += Size[irep];
    }
    // Else if j’s size is less than i’s size
    else {
        // Then move j under i
        Parent[jrep] = irep;

        // Increment i's size by j's size
        Size[irep] += Size[jrep];
    }
}

};

int main() { int n = 5; UnionFind unionFind(n); unionFind.unionBySize(0, 1); unionFind.unionBySize(2, 3); unionFind.unionBySize(0, 4); for (int i = 0; i < n; i++) { cout << "Element " << i << ": Representative = " << unionFind.find(i) << endl; } return 0; }

Java

// Java program for Union by Size with Path // Compression import java.util.Arrays;

class UnionFind {

private int[] Parent;
private int[] Size;

public UnionFind(int n)
{
    // Initialize Parent array
    Parent = new int[n];
    for (int i = 0; i < n; i++) {
        Parent[i] = i;
    }

    // Initialize Size array with 1s
    Size = new int[n];
    Arrays.fill(Size, 1);
}

// Function to find the representative (or the root
// node) for the set that includes i
public int find(int i) {
    
    int root = Parent[i];
  
    if (Parent[root] != root) {
        return Parent[i] = find(root);
    }
  
    return root;
}

// Unites the set that includes i and the set that
// includes j by size
public void unionBySize(int i, int j)
{
    // Find the representatives (or the root nodes) for
    // the set that includes i
    int irep = find(i);

    // And do the same for the set that includes j
    int jrep = find(j);

    // Elements are in the same set, no need to unite
    // anything.
    if (irep == jrep)
        return;

    // Get the size of i’s tree
    int isize = Size[irep];

    // Get the size of j’s tree
    int jsize = Size[jrep];

    // If i’s size is less than j’s size
    if (isize < jsize) {
        // Then move i under j
        Parent[irep] = jrep;

        // Increment j's size by i's size
        Size[jrep] += Size[irep];
    }
    // Else if j’s size is less than i’s size
    else {
        // Then move j under i
        Parent[jrep] = irep;

        // Increment i's size by j's size
        Size[irep] += Size[jrep];
    }
}

}

public class GFG {

public static void main(String[] args)
{
    // Example usage
    int n = 5;
    UnionFind unionFind = new UnionFind(n);

    // Perform union operations
    unionFind.unionBySize(0, 1);
    unionFind.unionBySize(2, 3);
    unionFind.unionBySize(0, 4);

    // Print the representative of each element after
    // unions
    for (int i = 0; i < n; i++) {
        System.out.println("Element " + i
                           + ": Representative = "
                           + unionFind.find(i));
    }
}

}

Python

class UnionFind: def init(self, n): self.Parent = list(range(n)) self.Size = [1] * n

# Function to find the representative (or the root
# node) for the set that includes i
def find(self, i):
    
    root = self.Parent[i]
  
    if self.Parent[root] != root:
        self.Parent[i] = self.find(root)
        return self.Parent[i]
  
    return root

# Unites the set that includes i and the set that
# includes j by size
def unionBySize(self, i, j):
  
    # Find the representatives (or the root nodes) for
    # the set that includes i
    irep = self.find(i)

    # And do the same for the set that includes j
    jrep = self.find(j)

    # Elements are in the same set, no need to unite
    # anything.
    if irep == jrep:
        return

    # Get the size of i’s tree
    isize = self.Size[irep]

    # Get the size of j’s tree
    jsize = self.Size[jrep]

    # If i’s size is less than j’s size
    if isize < jsize:
      
        # Then move i under j
        self.Parent[irep] = jrep

        # Increment j's size by i's size
        self.Size[jrep] += self.Size[irep]
        
    # Else if j’s size is less than i’s size
    else:
      
        # Then move j under i
        self.Parent[jrep] = irep

        # Increment i's size by j's size
        self.Size[irep] += self.Size[jrep]

n = 5 unionFind = UnionFind(n) unionFind.unionBySize(0, 1) unionFind.unionBySize(2, 3) unionFind.unionBySize(0, 4) for i in range(n): print(f'Element {i}: Representative = {unionFind.find(i)}')

C#

// C# program for Union by Size with Path Compression using System; using System.Linq;

class UnionFind { private int[] Parent; private int[] Size;

public UnionFind(int n)
{
    // Initialize Parent array
    Parent = new int[n];
    for (int i = 0; i < n; i++) {
        Parent[i] = i;
    }

    // Initialize Size array with 1s
    Size = new int[n];
    Array.Fill(Size, 1);
}

// Function to find the representative (or 
// the root node) for the set that includes i
public int Find(int i) {
  
    int root = Parent[i];
  
    if (Parent[root] != root) {
        return Parent[i] = Find(root);
    }
  
    return root;
}

// Unites the set that includes i and the set 
// that includes j by size
public void UnionBySize(int i, int j)
{
    // Find the representatives (or the root nodes) 
    // for the set that includes i
    int irep = Find(i);

    // And do the same for the set that includes j
    int jrep = Find(j);

    // Elements are in the same set, no need to unite anything.
    if (irep == jrep)
        return;

    // Get the size of i’s tree
    int isize = Size[irep];

    // Get the size of j’s tree
    int jsize = Size[jrep];

    // If i’s size is less than j’s size
    if (isize < jsize)
    {
        // Then move i under j
        Parent[irep] = jrep;

        // Increment j's size by i's size
        Size[jrep] += Size[irep];
    }
    // Else if j’s size is less than i’s size
    else
    {
        // Then move j under i
        Parent[jrep] = irep;

        // Increment i's size by j's size
        Size[irep] += Size[jrep];
    }
}

}

class GFG { public static void Main(string[] args) { int n = 5; UnionFind unionFind = new UnionFind(n); unionFind.UnionBySize(0, 1); unionFind.UnionBySize(2, 3); unionFind.UnionBySize(0, 4);

    // Print the representative of each element after unions
    for (int i = 0; i < n; i++)
    {
        Console.WriteLine("Element " + i + ": Representative = " 
                          + unionFind.Find(i));
    }
}

}

` JavaScript ``

class UnionFind { constructor(n) { this.Parent = Array.from({ length: n }, (_, i) => i); this.Size = Array(n).fill(1); }

// Function to find the representative (or the root
// node) for the set that includes i
find(i) {
    
    let root = this.Parent[i];

    if (this.Parent[root] !== root) {
        return this.Parent[i] = this.find(root);
    }
    
    return root;
}

// Unites the set that includes i and the set that
// includes j by size
unionBySize(i, j) {
    // Find the representatives (or the root nodes) for
    // the set that includes i
    const irep = this.find(i);

    // And do the same for the set that includes j
    const jrep = this.find(j);

    // Elements are in the same set, no need to unite
    // anything.
    if (irep === jrep) return;

    // Get the size of i’s tree
    const isize = this.Size[irep];

    // Get the size of j’s tree
    const jsize = this.Size[jrep];

    // If i’s size is less than j’s size
    if (isize < jsize) {
        // Then move i under j
        this.Parent[irep] = jrep;

        // Increment j's size by i's size
        this.Size[jrep] += this.Size[irep];
    } else {
        // Then move j under i
        this.Parent[jrep] = irep;

        // Increment i's size by j's size
        this.Size[irep] += this.Size[jrep];
    }
}

}

const n = 5; const unionFind = new UnionFind(n); unionFind.unionBySize(0, 1); unionFind.unionBySize(2, 3); unionFind.unionBySize(0, 4); for (let i = 0; i < n; i++) { console.log(Element <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>i</mi><mo>:</mo><mi>R</mi><mi>e</mi><mi>p</mi><mi>r</mi><mi>e</mi><mi>s</mi><mi>e</mi><mi>n</mi><mi>t</mi><mi>a</mi><mi>t</mi><mi>i</mi><mi>v</mi><mi>e</mi><mo>=</mo></mrow><annotation encoding="application/x-tex">{i}: Representative = </annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6595em;"></span><span class="mord"><span class="mord mathnormal">i</span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">:</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.8778em;vertical-align:-0.1944em;"></span><span class="mord mathnormal" style="margin-right:0.00773em;">R</span><span class="mord mathnormal">e</span><span class="mord mathnormal">p</span><span class="mord mathnormal">rese</span><span class="mord mathnormal">n</span><span class="mord mathnormal">t</span><span class="mord mathnormal">a</span><span class="mord mathnormal">t</span><span class="mord mathnormal">i</span><span class="mord mathnormal" style="margin-right:0.03588em;">v</span><span class="mord mathnormal">e</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span></span></span></span>{unionFind.find(i)}); }

``

Output

Element 0: Representative = 0 Element 1: Representative = 0 Element 2: Representative = 2 Element 3: Representative = 2 Element 4: Representative = 0