Union Find

Initialization

1

2

3

4

5

6

7

1

2

3

4

5

6

7

union(2,4), union(5,7)

union(4,7)

1

2

3

4

5

6

7

find(7) = 2

What is Union Find

  • A Data Structure that helps quickly union and find
  • Compare with HashMap
    • Hashmap is the best data structure to find
    • Hashmap is slow at union O(n)
  • Union find can be considered as trees (a forest)

An easy implementation

class UnionFind {
	List<Integer> list;
	public UnionFind(int n) {
		list = new ArrayList<>();
		for (int i = 0; i < n; i ++) {
			list.add(i);
		}
	}
    boolean union(int a, int b) {
        int ancesterA = find(a), ancesterB = find(b);
        if (ancesterA == ancesterB) return false; // need not to union.
        else {
            list.set(ancesterB, ancesterA);
            return true;
        }
    }
    int find(int k) {
        int i = k;
        while (i != list.get(i)) {
        	i = list.get(i); //Here i is the root.
        }
        return i;
    }
}

An easy implementation

class UnionFind:
    def __init__(self, n):
        self.list = [i for i in range(n)]
    
    def union(self, a, b):
        ancestor_a = self.find(a)
        ancestor_b = self.find(b)
        if ancestor_a == ancestor_b:
            return False
        else:
            self.list[ancestor_b] = ancestor_a
            return True
    
    def find(self, k):
        i = k
        while i != self.list[i]:
            i = self.list[i] # Here i is the root.
        return i

What does it do?

  • list is to keep each node's father
  • initialization: each node is an independent tree with its own as the father
  • find: keep finding above to get the root
  • union: make one node root to point to another root

Question it can solve

  • Graph connected components
  • check connections or groups for Sets

Surrounded Region

Given a 2D board containing 'X' and 'O' (the letter O), capture all regions surrounded by 'X'.

A region is captured by flipping all 'O's into 'X's in that surrounded region.

X X X X
X O O X
X X O X
X O X X

X X X X
X X X X
X X X X
X O X X

Surrounded Region

Before we use BFS or DFS to solve this question

But Union Find can also solve this question

 

 

Surrounded Region

class Solution {
    List<Integer> list = new ArrayList<>();
    List<Boolean> edge = new ArrayList<>();
    public void solve(char[][] board) {
        if (board.length == 0 || board[0].length == 0) return;
        int m = board.length;
        int n = board[0].length;
        for (int i =  0; i < m * n; i ++) {
            int x = i / n;
            int y = i % n;
            if ((x == 0 || x == m - 1 || y == 0 || y == n - 1) && (board[x][y] == 'O')) {
                edge.add(true);
            } else {
                edge.add(false);
            }
            list.add(i);
        }
        for (int i = 0; i < m * n; i ++) {
            int x = i / n;
            int y = i % n;
            if (x < m - 1 && board[x][y] == board[x + 1][y]) {
                union(i, i + n);
            }
            if (y < n - 1 && board[x][y] == board[x][y+1]) {
                union(i, i + 1);
            }
        }
        for (int i = 0; i < m * n; i ++) {
            int x = i / n;
            int y = i % n;
            if (board[x][y] == 'O' && !edge.get(find(i))) {
                board[x][y] = 'X';
            }
        }
    }

Surrounded Region

    boolean union(int a, int b) {
        int ancesterA = find(a), ancesterB = find(b);
        if (ancesterA == ancesterB) return false; // need not to union.
        else {
            list.set(ancesterB, ancesterA);
            if (edge.get(ancesterB)) {
                edge.set(ancesterA, true);
            }
            return true;
        }
    }
    
    int find(int k) {
        int i = k;
        while (i != list.get(i)) {
        	i = list.get(i); //Here i is the root.
        }
        return i;
    }
}

Surrounded Region

class Solution:
    def __init__(self):
        self.list = []
        self.edge = []
    
    def solve(self, board: List[List[str]]) -> None:
        if len(board) == 0 or len(board[0]) == 0:
            return
        m = len(board)
        n = len(board[0])
        
        for i in range(m * n):
            x = i // n
            y = i % n
            if (x == 0 or x == m - 1 or y == 0 or y == n - 1) and (board[x][y] == 'O'):
                self.edge.append(True)
            else:
                self.edge.append(False)
            self.list.append(i)
        
        for i in range(m * n):
            x = i // n
            y = i % n
            if x < m - 1 and board[x][y] == board[x + 1][y]:
                self.union(i, i + n)
            if y < n - 1 and board[x][y] == board[x][y + 1]:
                self.union(i, i + 1)
        
        for i in range(m * n):
            x = i // n
            y = i % n
            if board[x][y] == 'O' and not self.edge[self.find(i)]:
                board[x][y] = 'X'

Surrounded Region

    def union(self, a, b):
        ancestor_a = self.find(a)
        ancestor_b = self.find(b)
        if ancestor_a == ancestor_b:
            return False
        else:
            self.list[ancestor_b] = ancestor_a
            if self.edge[ancestor_b]:
                self.edge[ancestor_a] = True
            return True
    
    def find(self, k):
        i = k
        while i != self.list[i]:
            i = self.list[i] # Here i is the root.
        return i

Friend Circles

There are N students in a class. Some of them are friends, while some are not. Their friendship is transitive in nature. For example, if A is a directfriend of B, and B is a direct friend of C, then A is an indirect friend of C. And we defined a friend circle is a group of students who are direct or indirect friends.

Given a N*N matrix M representing the friend relationship between students in the class. If M[i][j] = 1, then the ith and jth students are directfriends with each other, otherwise not. And you have to output the total number of friend circles among all the students.

Friend Circles

Input:
[[1,1,0],
 [1,1,0],
 [0,0,1]]
Output: 2
Explanation:The 0th and 1st students are direct friends, so they are in a friend circle.
The 2nd student himself is in a friend circle. So return 2.

 

It is very intuitive that Union Find can solve the problem. We just need to keep doing the union and count after all how many sets there are.

Friend Circles

class Solution {
    List<Integer> list = new ArrayList<>();
    void union(int a, int b) {
        int ancesterA = find(a), ancesterB = find(b);
        if (ancesterA == ancesterB) return; // need not to union.
        else {
            list.set(ancesterB, ancesterA);
        }
    }
    int find(int k) {
        int i = k;
        while (i != list.get(i)) {
        	i = list.get(i); //Here i is the root.
        }
        return i;
    }
    public int findCircleNum(int[][] M) {
        int n = M.length;
        int count = n;
        for (int i = 0; i < n; i ++) {
            list.add(i);
        }
        for (int i = 0; i < n; i ++) {
            for (int j = 0; j < i; j ++) {
                if (M[i][j] == 1 && find(i) != find(j)) {
                    union(i, j);
                    count --;
                }
            }
        }
        return count;
    }
}

Friend Circles

class Solution:
    def __init__(self):
        self.list = []
    
    def union(self, a, b):
        ancestor_a = self.find(a)
        ancestor_b = self.find(b)
        if ancestor_a == ancestor_b:
            return
        else:
            self.list[ancestor_b] = ancestor_a
    
    def find(self, k):
        i = k
        while i != self.list[i]:
            i = self.list[i] # Here i is the root.
        return i
    
    def findCircleNum(self, M):
        n = len(M)
        count = n
        for i in range(n):
            self.list.append(i)
        
        for i in range(n):
            for j in range(i):
                if M[i][j] == 1 and self.find(i) != self.find(j):
                    self.union(i, j)
                    count -= 1
        
        return count

Potential improvement

  • When you keep union, the tree could be very tall
  • maintain a height so when union happens, the smaller tree union to the bigger tree. So it can help keep the tree height as small as possible

Homework