Concept

This explains well This virtual explanation too

https://techblog-history-younghunjo1.tistory.com/257 Python version

Template

def find_parent(parent,x):
    if parent[x]!=x:
        # path compression
        parent[x] = find_parent(parent,parent[x])
      
    #   without path compression
        return find_parent(parent, parent[x])
    return parent[x]

def union(parent,a,b):
    a = find_parent(parent,a)
    b = find_parent(parent,b)
    parent[a] = b
public class Union_Find {

    public static int[] parent = new int[1000001];
  
    public static int find(int x) {
      if (x == parent[x])
        return x;
      else
        return parent[x] = find(parent[x]);
    }
  
    public static void union(int x, int y) {
      x = find(x);
      y = find(y);
      // 같은 부모를 가지고 있지 않을 때
      if (x != y) {
        // y가 x 보다 크다는 것을 가정한다면 아래와 같이 표현
        parent[y] = x;
        // 더 작은 값으로 넣어 줄 때 다음과 같이도 표현 가능
        /*
         * if(x < y) parent[y] = x; else parent[x] = y;
         */
      }
    }
  
    // 같은 부모 노드를 가지는지 확인
    public static boolean isSameParent(int x, int y) {
      x = find(x);
      y = find(y);
      if (x == y)
        return true;
      else
        return false;
    }
  
    public static void main(String[] args) {
      for (int i = 1; i <= 8; i++) {
        parent[i] = i;
      }
      union(1, 2);
      union(2, 3);
      System.out.println("1과 3은 연결되어 있나요? " + isSameParent(1, 3));
  
    }

}

Leetcode’s template is:

class UnionFind {
    private int[] root;

    public UnionFind(int size) {
        root = new int[size];
        for (int i = 0; i < size; i++) {
            root[i] = i;
        }
    }

    public int find(int x) {
        while (x != root[x]) {
            x = root[x];
        }
        return x;
    }

    public void union(int x, int y) {
        int rootX = find(x);
        int rootY = find(y);
        if (rootX != rootY) {
            root[rootY] = rootX;
        }
    }

    public boolean connected(int x, int y) {
        return find(x) == find(y);
    }
}

Example

Let’s take this example First to understand reason to use union rank, let’s explore DFS and BFS solutions first

DFS

class Solution {
    private boolean seen;
    public boolean validPath(int n, int[][] edges, int start, int end) {
        boolean[] visited = new boolean[n];
        HashSet<Integer>[] graph = new HashSet[n];

        for(int i=0; i<n; i++){
            graph[i] = new HashSet<Integer>();
        }

        for(int[] edge:edges){
            graph[edge[0]].add(edge[1]);
            graph[edge[1]].add(edge[0]);
        }
        if(graph[start].contains(end)){
            return true;
        }
        seen = false;
        dfs(graph,visited,start,end);
        return seen;
    }

    private void dfs(HashSet<Integer>[] graph, boolean[] visited, int start, int end){
        if(!visited[start]&& !seen){
            if(start==end){
                seen=true;
                return;
            }
            visited[start] = true;
            for(Integer neighbour: graph[start]){
                dfs(graph,visited,neighbour,end);
            }
        }
    }
}

BFS

class Solution {
    public boolean validPath(int n, int[][] edges, int start, int end) {
        boolean[] visited = new boolean[n];
        HashSet<Integer>[] graph = new HashSet[n];
        int i, j;
        
        for(i = 0; i < n; i++){
            graph[i] = new HashSet<Integer>();
        }
        
        for(int[] edge : edges){
            graph[edge[0]].add(edge[1]);
            graph[edge[1]].add(edge[0]);
        }
		
        if(graph[start].contains(end)){  // direct connection exists
            return true;
        }

        Queue<Integer> queue=new LinkedList<>();
        queue.offer(start);
        visited[start] = true;

        while(!queue.isEmpty()){
            int a = queue.size();
            int current = queue.poll();
            if(current ==end) return true;
            for(Integer neighbour:graph[current]){
                if(!visited[neighbour]){
                    visited[neighbour] = true;
                    queue.offer(neighbour);
                }
            }
        }
        return false;
    }
}

Union Find

class DisjointSetUnion{
    private int[] parent;
    private int N;
    public DisjointSetUnion(int n){
        this.N = n;
        this.parent = new int[this.N];
        for(int i=0; i<this.N; i++){
            this.parent[i] = i;
        }
    }
     public boolean areConnected(int u, int v){
         return find(u)==find(v);
     }
     public void union(int u, int v){
         if(u!=v){
             int a = find(u);
             int b = find(v);
             parent[a]=b;
         }
     }
     private int find(int u){
         if(parent[u]==u){
             return u;
         }
         else return parent[u] = find(parent[u]);
     }

}

class Solution {
    public boolean validPath(int n, int[][] edges, int start, int end) {
        DisjointSetUnion set = new DisjointSetUnion(n);
        for(int[] edge : edges){
            set.union(edge[0], edge[1]);
        }
        
        return set.areConnected(start, end);
    }
}