Union By Rank and Path Compression in Union-Find Algorithm
Last Updated :
24 Mar, 2023
In the previous post, we introduced union find algorithm and used it to detect cycles in a graph. We used the following union() and find() operations for subsets.
C++
int find( int parent[], int i)
{
if (parent[i] == -1)
return i;
return find(parent, parent[i]);
}
void Union( int parent[], int x, int y)
{
int xset = find(parent, x);
int yset = find(parent, y);
parent[xset] = yset;
}
|
Java
static int find( int parent[], int i)
{
if (parent[i] == - 1 )
return i;
return find(parent, parent[i]);
}
static void Union( int parent[], int x, int y)
{
int xset = find(parent, x);
int yset = find(parent, y);
parent[xset] = yset;
}
|
Python3
def find(parent, i):
if (parent[i] = = - 1 ):
return i
return find(parent, parent[i])
def Union(parent, x, y):
xset = find(parent, x)
yset = find(parent, y)
parent[xset] = yset
|
C#
static int find( int []parent, int i)
{
if (parent[i] == -1)
return i;
return find(parent, parent[i]);
}
static void Union( int []parent, int x, int y)
{
int xset = find(parent, x);
int yset = find(parent, y);
parent[xset] = yset;
}
|
Javascript
<script>
function find(parent, i)
{
if (parent[i] == -1)
return i;
return find(parent, parent[i]);
}
function Union(parent, x, y)
{
let xset = find(parent, x);
let yset = find(parent, y);
parent[xset] = yset;
}
<script>
|
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 worst case scenario.
Let there be 4 elements 0, 1, 2, 3
Initially, all elements are single element subsets.
0 1 2 3
Do Union(0, 1)
1 2 3
/
0
Do Union(1, 2)
2 3
/
1
/
0
Do Union(2, 3)
3
/
2
/
1
/
0
The above operations can be optimized to O(Log n) in the worst case. The idea is to always attach a smaller depth tree under the root of the deeper tree. This technique is called union by rank. The term rank is preferred instead of height because if the path compression technique (we have discussed it below) is used, then the rank is not always equal to height. Also, the size (in place of height) of trees can also be used as rank. Using size as rank also yields worst-case time complexity as O(Logn).
Let us see the above example with union by rank
Initially, all elements are single element subsets.
0 1 2 3
Do Union(0, 1)
1 2 3
/
0
Do Union(1, 2)
1 3
/ \
0 2
Do Union(2, 3)
1
/ | \
0 2 3
The second optimization to naive method is Path Compression. 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.
Let the subset {0, 1, .. 9} be represented as below and find() is called
for element 3.
9
/ | \
4 5 6
/ / \
0 7 8
/
3
/ \
1 2
When find() is called for 3, we traverse up and find 9 as representative
of this subset. With path compression, we also make 3 and 0 as the child of 9 so
that when find() is called next time for 0, 1, 2 or 3, the path to root is reduced.
--------9-------
/ / / \ \
0 4 5 6 3
/ \ / \
7 8 1 2
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).
Following is union by rank and path compression-based implementation to find a cycle in a graph.
C++
#include <bits/stdc++.h>
using namespace std;
struct Edge {
int src, dest;
};
struct Graph {
int V, E;
struct Edge* edge;
};
struct subset {
int parent;
int rank;
};
struct Graph* createGraph( int V, int E)
{
struct Graph* graph = ( struct Graph*) malloc ( sizeof ( struct Graph));
graph->V = V;
graph->E = E;
graph->edge = ( struct Edge*) malloc (graph->E * sizeof ( struct Edge));
return graph;
}
int find( struct subset subsets[], int i)
{
if (subsets[i].parent != i)
subsets[i].parent = find(subsets, subsets[i].parent);
return subsets[i].parent;
}
void Union( struct subset subsets[], int xroot, int yroot)
{
if (subsets[xroot].rank < subsets[yroot].rank)
subsets[xroot].parent = yroot;
else if (subsets[xroot].rank > subsets[yroot].rank)
subsets[yroot].parent = xroot;
else {
subsets[yroot].parent = xroot;
subsets[xroot].rank++;
}
}
int isCycle( struct Graph* graph)
{
int V = graph->V;
int E = graph->E;
struct subset* subsets
= ( struct subset*) malloc (V * sizeof ( struct subset));
for ( int v = 0; v < V; ++v) {
subsets[v].parent = v;
subsets[v].rank = 0;
}
for ( int e = 0; e < E; ++e) {
int x = find(subsets, graph->edge[e].src);
int y = find(subsets, graph->edge[e].dest);
if (x == y)
return 1;
Union(subsets, x, y);
}
return 0;
}
int main()
{
int V = 3, E = 3;
struct Graph* graph = createGraph(V, E);
graph->edge[0].src = 0;
graph->edge[0].dest = 1;
graph->edge[1].src = 1;
graph->edge[1].dest = 2;
graph->edge[2].src = 0;
graph->edge[2].dest = 2;
if (isCycle(graph))
cout << "Graph contains cycle" ;
else
cout << "Graph doesn't contain cycle" ;
return 0;
}
|
C
#include <stdio.h>
#include <stdlib.h>
typedef struct Edge {
int src, dest;
}Edge;
typedef struct Graph {
int V, E;
struct Edge* edge;
}Graph;
typedef struct subset {
int parent;
int rank;
}subset;
Graph* createGraph( int V, int E)
{
Graph* graph = (Graph*) malloc ( sizeof (Graph));
graph->V = V;
graph->E = E;
graph->edge = (Edge*) malloc (graph->E * sizeof (Edge));
return graph;
}
int find(subset subsets[], int i)
{
if (subsets[i].parent != i)
subsets[i].parent = find(subsets, subsets[i].parent);
return subsets[i].parent;
}
void Union(subset subsets[], int xroot, int yroot)
{
if (subsets[xroot].rank < subsets[yroot].rank)
subsets[xroot].parent = yroot;
else if (subsets[xroot].rank > subsets[yroot].rank)
subsets[yroot].parent = xroot;
else {
subsets[yroot].parent = xroot;
subsets[xroot].rank++;
}
}
int isCycle(Graph* graph)
{
int V = graph->V;
int E = graph->E;
subset* subsets = (subset*) malloc (V * sizeof (subset));
for ( int v = 0; v < V; ++v) {
subsets[v].parent = v;
subsets[v].rank = 0;
}
for ( int e = 0; e < E; ++e) {
int x = find(subsets, graph->edge[e].src);
int y = find(subsets, graph->edge[e].dest);
if (x == y)
return 1;
Union(subsets, x, y);
}
return 0;
}
int main()
{
int V = 3, E = 3;
Graph* graph = createGraph(V, E);
graph->edge[0].src = 0;
graph->edge[0].dest = 1;
graph->edge[1].src = 1;
graph->edge[1].dest = 2;
graph->edge[2].src = 0;
graph->edge[2].dest = 2;
if (isCycle(graph))
printf ( "Graph contains cycle" );
else
printf ( "Graph doesn't contain cycle" );
return 0;
}
|
Java
class Graph
{
int V, E;
Edge[] edge;
Graph( int nV, int nE)
{
V = nV;
E = nE;
edge = new Edge[E];
for ( int i = 0 ; i < E; i++)
{
edge[i] = new Edge();
}
}
class Edge
{
int src, dest;
}
class subset
{
int parent;
int rank;
}
int find(subset[] subsets, int i)
{
if (subsets[i].parent != i)
subsets[i].parent
= find(subsets, subsets[i].parent);
return subsets[i].parent;
}
void Union(subset[] subsets, int x, int y)
{
int xroot = find(subsets, x);
int yroot = find(subsets, y);
if (subsets[xroot].rank < subsets[yroot].rank)
subsets[xroot].parent = yroot;
else if (subsets[yroot].rank < subsets[xroot].rank)
subsets[yroot].parent = xroot;
else {
subsets[xroot].parent = yroot;
subsets[yroot].rank++;
}
}
int isCycle(Graph graph)
{
int V = graph.V;
int E = graph.E;
subset[] subsets = new subset[V];
for ( int v = 0 ; v < V; v++) {
subsets[v] = new subset();
subsets[v].parent = v;
subsets[v].rank = 0 ;
}
for ( int e = 0 ; e < E; e++) {
int x = find(subsets, graph.edge[e].src);
int y = find(subsets, graph.edge[e].dest);
if (x == y)
return 1 ;
Union(subsets, x, y);
}
return 0 ;
}
public static void main(String[] args)
{
int V = 3 , E = 3 ;
Graph graph = new Graph(V, E);
graph.edge[ 0 ].src = 0 ;
graph.edge[ 0 ].dest = 1 ;
graph.edge[ 1 ].src = 1 ;
graph.edge[ 1 ].dest = 2 ;
graph.edge[ 2 ].src = 0 ;
graph.edge[ 2 ].dest = 2 ;
if (graph.isCycle(graph) == 1 )
System.out.println( "Graph contains cycle" );
else
System.out.println(
"Graph doesn't contain cycle" );
}
}
|
Python3
from collections import defaultdict
class Graph:
def __init__( self , num_of_v):
self .num_of_v = num_of_v
self .edges = defaultdict( list )
def add_edge( self , u, v):
self .edges[u].append(v)
class Subset:
def __init__( self , parent, rank):
self .parent = parent
self .rank = rank
def find(subsets, node):
if subsets[node].parent ! = node:
subsets[node].parent = find(subsets, subsets[node].parent)
return subsets[node].parent
def union(subsets, u, v):
if subsets[u].rank > subsets[v].rank:
subsets[v].parent = u
elif subsets[v].rank > subsets[u].rank:
subsets[u].parent = v
else :
subsets[v].parent = u
subsets[u].rank + = 1
def isCycle(graph):
subsets = []
for u in range (graph.num_of_v):
subsets.append(Subset(u, 0 ))
for u in graph.edges:
u_rep = find(subsets, u)
for v in graph.edges[u]:
v_rep = find(subsets, v)
if u_rep = = v_rep:
return True
else :
union(subsets, u_rep, v_rep)
g = Graph( 3 )
g.add_edge( 0 , 1 )
g.add_edge( 1 , 2 )
g.add_edge( 0 , 2 )
if isCycle(g):
print ( 'Graph contains cycle' )
else :
print ( 'Graph does not contain cycle' )
|
C#
using System;
class Graph {
public int V, E;
public Edge[] edge;
public Graph( int nV, int nE)
{
V = nV;
E = nE;
edge = new Edge[E];
for ( int i = 0; i < E; i++)
{
edge[i] = new Edge();
}
}
public class Edge {
public int src, dest;
}
class subset
{
public int parent;
public int rank;
}
int find(subset[] subsets, int i)
{
if (subsets[i].parent != i)
subsets[i].parent
= find(subsets, subsets[i].parent);
return subsets[i].parent;
}
void Union(subset[] subsets, int x, int y)
{
int xroot = find(subsets, x);
int yroot = find(subsets, y);
if (subsets[xroot].rank < subsets[yroot].rank)
subsets[xroot].parent = yroot;
else if (subsets[yroot].rank < subsets[xroot].rank)
subsets[yroot].parent = xroot;
else {
subsets[xroot].parent = yroot;
subsets[yroot].rank++;
}
}
int isCycle(Graph graph)
{
int V = graph.V;
int E = graph.E;
subset[] subsets = new subset[V];
for ( int v = 0; v < V; v++)
{
subsets[v] = new subset();
subsets[v].parent = v;
subsets[v].rank = 0;
}
for ( int e = 0; e < E; e++)
{
int x = find(subsets, graph.edge[e].src);
int y = find(subsets, graph.edge[e].dest);
if (x == y)
return 1;
Union(subsets, x, y);
}
return 0;
}
static public void Main(String[] args)
{
int V = 3, E = 3;
Graph graph = new Graph(V, E);
graph.edge[0].src = 0;
graph.edge[0].dest = 1;
graph.edge[1].src = 1;
graph.edge[1].dest = 2;
graph.edge[2].src = 0;
graph.edge[2].dest = 2;
if (graph.isCycle(graph) == 1)
Console.WriteLine( "Graph contains cycle" );
else
Console.WriteLine(
"Graph doesn't contain cycle" );
}
}
|
Javascript
<script>
let V, E;
let edge;
function Graph(nV,nE)
{
V = nV;
E = nE;
edge = new Array(E);
for (let i = 0; i < E; i++)
{
edge[i] = new Edge();
}
}
class Edge
{
constructor()
{
this .src=0;
this .dest=0;
}
}
class subset
{
constructor()
{
this .parent=0;
this .rank=0;
}
}
function find(subsets,i)
{
if (subsets[i].parent != i)
subsets[i].parent
= find(subsets, subsets[i].parent);
return subsets[i].parent;
}
function Union(subsets,x,y)
{
let xroot = find(subsets, x);
let yroot = find(subsets, y);
if (subsets[xroot].rank < subsets[yroot].rank)
subsets[xroot].parent = yroot;
else if (subsets[yroot].rank < subsets[xroot].rank)
subsets[yroot].parent = xroot;
else {
subsets[xroot].parent = yroot;
subsets[yroot].rank++;
}
}
function isCycle()
{
let subsets = new Array(V);
for (let v = 0; v < V; v++) {
subsets[v] = new subset();
subsets[v].parent = v;
subsets[v].rank = 0;
}
for (let e = 0; e < E; e++) {
let x = find(subsets, edge[e].src);
let y = find(subsets, edge[e].dest);
if (x == y)
return 1;
Union(subsets, x, y);
}
return 0;
}
V = 3, E = 3;
Graph(V, E);
edge[0].src = 0;
edge[0].dest = 1;
edge[1].src = 1;
edge[1].dest = 2;
edge[2].src = 0;
edge[2].dest = 2;
if (isCycle() == 1)
document.write( "Graph contains cycle" );
else
document.write(
"Graph doesn't contain cycle" );
</script>
|
Output
Graph contains cycle
Time complexity: O(ElogV) where E is the number of edges in the graph and V is the number of vertices.
Space complexity: O(V), where V is the number of vertices. This is because we are using an array of subsets to store the representative elements of each vertex, and the size of this array is proportional to the number of vertices.
Related Articles :
Please Login to comment...