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:
- Merging two disjoint sets to a single set using Union operation.
- Finding representative of a disjoint set using Find operation.
- Check if two elements belong to same set or not. We mainly find representative of both and check if same.
Consider a situation with a number of persons and the following tasks to be performed on them:
- Add a new friendship relation, i.e. a person x becomes the friend of another person y i.e adding new element to a set.
- Find whether individual x is a friend of individual y (direct or indirect friend)
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 <-> jGiven queries like whether a is a friend of d or not.
To answer above queries, 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}
Two key points to be considered are:
- How to Resolve sets? Initially, all elements belong to different sets. After working on the given relations, we select a member as a representative.
- Check if 2 persons are in the same group? If representatives of two individuals are the same, then they are friends.
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:
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).
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.
#include <bits/stdc++.h>
using namespace std;
class MyDSU {
private:
vector<int> parent;
public:
MyDSU(int n) {
parent.resize(n);
// Initialize each element as its own representative
for (int i = 0; i < n; i++)
parent[i] = i;
}
// Find the representative using path compression
int find(int x) {
if (parent[x] == x)
return x;
return parent[x] = find(parent[x]);
}
// Merge the group containing x into the group containing y
void unionSet(int x, int y) {
int rootX = find(x);
int rootY = find(y);
if (rootX != rootY)
parent[rootX] = rootY;
}
};
int main() {
int n = 5;
// Query types:
// [1, x, y] -> unionSet(x, y)
// [2, x] -> find(x)
vector<vector<int>> queries = {
{2, 4},
{2, 1},
{1, 3, 1},
{2, 3}
};
MyDSU dsu(n);
vector<int> result;
for (const auto& q : queries) {
int type = q[0];
if (type == 1) {
// unionSet(x, y)
int x = q[1];
int y = q[2];
dsu.unionSet(x, y);
} else if (type == 2) {
// find(x)
int x = q[1];
result.push_back(dsu.find(x));
}
}
// Print output formatted as [4, 1, 1]
cout << "[";
for (size_t i = 0; i < result.size(); i++) {
cout << result[i];
if (i + 1 < result.size()) {
cout << ", ";
}
}
cout << "]\n";
return 0;
}
import java.util.*;
class MyDSU {
private int[] parent;
public MyDSU(int n)
{
parent = new int[n];
// Initialize each element as its own representative
for (int i = 0; i < n; i++) {
parent[i] = i;
}
}
// Find the representative using path compression
public int find(int x)
{
if (parent[x] != x) {
parent[x] = find(parent[x]);
}
return parent[x];
}
// Merge the group containing x into the group
// containing y
public void unionSet(int x, int y)
{
int rootX = find(x);
int rootY = find(y);
if (rootX != rootY) {
parent[rootX] = rootY;
}
}
}
class GFG {
public static void main(String[] args)
{
int n = 5;
// Query types:
// [1, x, y] -> unionSet(x, y)
// [2, x] -> find(x)
int[][] queries
= { { 2, 4 }, { 2, 1 }, { 1, 3, 1 }, { 2, 3 } };
MyDSU dsu = new MyDSU(n);
List<Integer> result = new ArrayList<>();
for (int[] q : queries) {
int type = q[0];
if (type == 1) {
// unionSet(x, y)
int x = q[1];
int y = q[2];
dsu.unionSet(x, y);
}
else if (type == 2) {
// find(x)
int x = q[1];
result.add(dsu.find(x));
}
}
// Print output formatted as [4, 1, 1]
System.out.print("[");
for (int i = 0; i < result.size(); i++) {
System.out.print(result.get(i));
if (i + 1 < result.size()) {
System.out.print(", ");
}
}
System.out.println("]");
}
}
class MyDSU:
def __init__(self, n: int) -> None:
self.parent = [0] * n
# Initialize each element as its own representative.
for i in range(n):
self.parent[i] = i
# Find the representative using path compression
def find(self, x: int) -> int:
if self.parent[x] == x:
return x
self.parent[x] = self.find(self.parent[x])
return self.parent[x]
# Merge the group containing x into the group containing y
def unionSet(self, x: int, y: int) -> None:
rootX = self.find(x)
rootY = self.find(y)
if rootX != rootY:
self.parent[rootX] = rootY
def main():
n = 5
# Query types:
# [1, x, y] -> unionSet(x, y)
# [2, x] -> find(x)
queries = [
[2, 4],
[2, 1],
[1, 3, 1],
[2, 3]
]
dsu = MyDSU(n)
result = []
for q in queries:
query_type = q[0]
if query_type == 1:
# unionSet(x, y)
x = q[1]
y = q[2]
dsu.unionSet(x, y)
elif query_type == 2:
# find(x)
x = q[1]
result.append(dsu.find(x))
# Print output formatted as [4, 1, 1]
print("[" + ", ".join(map(str, result)) + "]")
# Driver Code
if __name__ == "__main__":
main()
using System;
using System.Collections.Generic;
class MyDSU {
private int[] parent;
public MyDSU(int n)
{
parent = new int[n];
// Initialize each element as its own representative
for (int i = 0; i < n; i++) {
parent[i] = i;
}
}
// Find the representative using path compression
public int find(int x)
{
if (parent[x] != x) {
parent[x] = find(parent[x]);
}
return parent[x];
}
// Merge the group containing x into the group
// containing y
public void unionSet(int x, int y)
{
int rootX = find(x);
int rootY = find(y);
if (rootX != rootY) {
parent[rootX] = rootY;
}
}
}
class GFG {
public static void Main()
{
int n = 5;
// Query types:
// [1, x, y] -> unionSet(x, y)
// [2, x] -> find(x)
int[][] queries = new int[][] {
new int[] { 2, 4 }, new int[] { 2, 1 },
new int[] { 1, 3, 1 }, new int[] { 2, 3 }
};
MyDSU dsu = new MyDSU(n);
List<int> result = new List<int>();
foreach(var q in queries)
{
int type = q[0];
if (type == 1) {
// unionSet(x, y)
int x = q[1];
int y = q[2];
dsu.unionSet(x, y);
}
else if (type == 2) {
// find(x)
int x = q[1];
result.Add(dsu.find(x));
}
}
// Print output formatted as [4, 1, 1]
Console.WriteLine("[" + string.Join(", ", result)
+ "]");
}
}
class MyDSU {
constructor(n)
{
this.parent = new Array(n);
// Initialize each element as its own
// representative.
for (let i = 0; i < n; i++) {
this.parent[i] = i;
}
}
// Find the representative using path compression
find(x)
{
if (this.parent[x] === x)
return x;
return this.parent[x] = this.find(this.parent[x]);
}
// Merge the group containing x into the group
// containing y
unionSet(x, y)
{
let rootX = this.find(x);
let rootY = this.find(y);
if (rootX !== rootY) {
this.parent[rootX] = rootY;
}
}
}
function main()
{
let n = 5;
// Query types:
// [1, x, y] -> unionSet(x, y)
// [2, x] -> find(x)
let queries =
[ [ 2, 4 ], [ 2, 1 ], [ 1, 3, 1 ], [ 2, 3 ] ];
let dsu = new MyDSU(n);
let result = [];
for (let q of queries) {
let queryType = q[0];
if (queryType === 1) {
// unionSet(x, y)
let x = q[1];
let y = q[2];
dsu.unionSet(x, y);
}
else if (queryType === 2) {
// find(x)
let x = q[1];
result.push(dsu.find(x));
}
}
// Print output formatted as [4, 1, 1]
console.log("[" + result.join(", ") + "]");
}
// Driver Code
main();
Output
[4, 1, 1]
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.
The above operations can be optimized using Union by Rank and Path Compression.