Disjoint Set Data Structure



Disjoint set also known as union-find data structure. It is a type of data structure that keeps track of a collection of elements that are partitioned into multiple non-overlapping (one element can be in only one set) disjoint sets.

It provides operations for adding (merging) two sets, finding the representative of the set to which an element belongs, and finding if two elements are in the same set.

Imagine you have a group of students. Initially, each student is in his/her own group. Over time, some students form their own groups due to common interests, and these groups can merge with other groups.

For example,

Initially :  {A}, {B}, {C}, {D}, {E} (Each student is in his/her own group)
Then A and B form a group : {A, B}, {C}, {D}, {E}
Then C and D form a group : {A, B}, {C, D}, {E}
Then A, B, C, D form a group : {A, B, C, D}, {E}

In the above example, we can see that the students are partitioned into multiple disjoint sets. They also merge with other sets to form a bigger set. Here, we can use disjoint set data structure to keep track of these sets.

Key Features of Disjoint Set

Following are the key features of disjoint set data structure:

  • Non-overlapping sets: Each element can belong to only one set.
  • Dynamic merging: We can merge two sets into one using Union operation.
  • Efficient querying: With the find operation, we can quickly find which set an element belongs to.

Operations on Disjoint Set

Following are the operations that can be performed on disjoint set:

  • Find(x): Find the representative of the set to which element x belongs.
  • Union(x, y): Merge the sets to which elements x and y belong.
  • MakeSet(x): Create a new set with element x.

Implementation of Disjoint Set

Disjoint set can be implemented using the following data structures:

  • Array: We can use an array to store the parent of each element. The parent of an element is the representative of the set to which the element belongs.
  • Rank: We can use rank to optimize the union operation. Rank is the height of the tree rooted at an element. We always attach the smaller tree to the larger tree to keep the height of the tree small.

Example code for Disjoint Set using Array

Following is the example code for disjoint set using array:

In order to implement disjoint set using array, we need to create a structure that contains an array to store the parent of each element and the number of elements in the set. We will also cover the following operations −

  • MakeSet(x)
  • Find(x)
  • Union(x, y)

These operations will help us to create a new set with element x, find the representative of the set to which element x belongs and merge the sets to which elements x and y belong.

Algorithm

We need to follow the following steps to implement disjoint set using array:

1. Create a new set with element x using MakeSet(x) operation.
2. Find the representative of the set to which element x belongs using Find(x) operation.
3. Merge the sets to which elements x and y belong using Union(x, y) operation.

Code

Following is code for implementing disjoint set using array:

#include <stdio.h>
#include <stdlib.h>

// Disjoint set data structure
struct DisjointSet{
   int *parent;
   int n;
};

// Create a new set with element x
void MakeSet(struct DisjointSet *ds, int x){
   ds->parent[x] = x;
}

// Find the representative of the set to which element x belongs
int Find(struct DisjointSet *ds, int x){
   if(ds->parent[x] == x)
      return x;
   return Find(ds, ds->parent[x]);
}

// Merge the sets to which elements x and y belong
void Union(struct DisjointSet *ds, int x, int y){
   int xset = Find(ds, x);
   int yset = Find(ds, y);
   ds->parent[xset] = yset;
}

int main(){
   struct DisjointSet ds;
   int n = 5;
   ds.n = n;
   ds.parent = (int *)malloc(n * sizeof(int));
   for(int i = 0; i < n; i++)
      MakeSet(&ds, i);
   Union(&ds, 0, 1);
   Union(&ds, 2, 3);
   Union(&ds, 0, 2);
   for(int i = 0; i < n; i++)
      printf("%d ", ds.parent[i]);
   return 0;
}

Output

Following is the output of the above code:

1 3 3 3 4
#include <iostream>
#include <vector>
using namespace std;

// Disjoint set data structure
struct DisjointSet{
   vector<int> parent;
   int n;
};

// Create a new set with element x
void MakeSet(struct DisjointSet *ds, int x){
   ds->parent[x] = x;
}

// Find the representative of the set to which element x belongs
int Find(struct DisjointSet *ds, int x){
   if(ds->parent[x] == x)
      return x;
   return Find(ds, ds->parent[x]);
}

// Merge the sets to which elements x and y belong
void Union(struct DisjointSet *ds, int x, int y){
   int xset = Find(ds, x);
   int yset = Find(ds, y);
   ds->parent[xset] = yset;
}

int main(){
   struct DisjointSet ds;
   int n = 5;
   ds.n = n;
   ds.parent.resize(n);
   for(int i = 0; i < n; i++)
      MakeSet(&ds, i);
   Union(&ds, 0, 1);
   Union(&ds, 2, 3);
   Union(&ds, 0, 2);
   for(int i = 0; i < n; i++)
      cout << ds.parent[i] << " ";
   return 0;
}

Output

Following is the output of the above code:

1 3 3 3 4
import java.util.*;

// Disjoint set data structure
class DisjointSet{
   int[] parent;
   int n;

   DisjointSet(int n){
      this.n = n;
      parent = new int[n];
      for(int i = 0; i < n; i++)
         parent[i] = i;
   }

   // Create a new set with element x
   void MakeSet(int x){
      parent[x] = x;
   }

   // Find the representative of the set to which element x belongs
   int Find(int x){
      if(parent[x] == x)
         return x;
      return Find(parent[x]);
   }

   // Merge the sets to which elements x and y belong
   void Union(int x, int y){
      int xset = Find(x);
      int yset = Find(y);
      parent[xset] = yset;
   }
}

public class Main{
   public static void main(String[] args){
      int n = 5;
      DisjointSet ds = new DisjointSet(n);
      ds.Union(0, 1);
      ds.Union(2, 3);
      ds.Union(0, 2);
      for(int i = 0; i < n; i++)
         System.out.print(ds.parent[i] + " ");
   }
}

Output

Following is the output of the above code:

1 3 3 3 4
# Disjoint set data structure
class DisjointSet:
   def __init__(self, n):
      self.parent = [i for i in range(n)]
      self.n = n

   # Create a new set with element x
   def MakeSet(self, x):
      self.parent[x] = x

   # Find the representative of the set to which element x belongs
   def Find(self, x):
      if self.parent[x] == x:
         return x
      return self.Find(self.parent[x])

   # Merge the sets to which elements x and y belong
   def Union(self, x, y):
      xset = self.Find(x)
      yset = self.Find(y)
      self.parent[xset] = yset

n = 5
ds = DisjointSet(n)
ds.Union(0, 1)
ds.Union(2, 3)
ds.Union(0, 2)
for i in range(n):
   print(ds.parent[i], end = " ")

Output

Following is the output of the above code:

1 3 3 3 4

Complexity of Disjoint Set Operations

Following are the time complexities of disjoint set operations:

  • Find(x): O(log n) where n is the number of elements.
  • Union(x, y): O(log n) where n is the number of elements.
  • MakeSet(x): O(1)

Applications of Disjoint Set

Following are the applications of disjoint set data structure:

  • Connected Components: Disjoint set can be used to find connected components in a graph.
  • Dynamic connectivity: It is also used to find if two elements are connected in a graph.
  • Image processing: It is used to find connected components in an image.
  • Kruskal's algorithm: We can use disjoint set to find minimum spanning tree using Kruskal's algorithm.
  • Network connectivity: You can check if two computers are connected in a network.
Advertisements