Given a set of \(N\) elements, support two operations:
Connection command connect(a, b): directly connect two elements, a and b, with an edge
Connection query isConnected(a, b): returns true if there is a path connecting two elements, a and b?
Is there a path connecting cyan and pink elements?
Applications involve manipulating elements of all types
When programming, convenient to name elements 0 to N-1.
We model "is connected to" as an equivalence relation, which is reflexive, symmetric, and transitive.
p is connected to pp is connected to q, then q is connected to pp is connected to q and q is connected to r, then p is connected to rWhich is not a property of equivalence relation?
Example:
|
3 disjoint sets / connected components \[ \{0\}\ \{1,4,5\}\ \{2,3,6,7\} \] |
![]() |
union(p, q)p and q with their unionfind(p)p?isConnected(p, q)find(p) == find(q)\[\{0\}\ \{1,4,5\}\ \{2,3,6,7\}\quad\underset{\textrm{union}(2,5)}{\Rightarrow}\quad\{0\}\ \{1,2,3,4,5,6,7\}\]
find(5) != find(6) union(2, 5) // 3 disjoint sets -> 2 disjoint sets find(5) == find(6)
How to model the dynamic-connectivity problem using union-find?
Maintain disjoint sets that correspond to connected components
union(2, 5)
![]() |
![]() |
Goal: design an efficient union-find data type

public class UF {
// initialize union-find data structure with N singleton sets (0 to N-1)
UF(int N) { ... }
// merge sets containing elements p and q
void union(int p, int q) { ... }
// identifier for set containing element p (0 to N-1)
int find(int p) { ... }
}
public static void main(String[] args) {
int N = StdIn.readInt();
UF uf = new UF(N);
while(!StdIn.isEmpty()) {
int p = StdIn.readInt();
int q = StdIn.readInt();
if(uf.find(p) != uf.find(q)) {
uf.union(p, q);
StdOut.println(p + " " + q);
}
}
}
Note with input below, lines 7, 11, and 12 (highlighted) are already connected and therefore will not print.
|
Input: 10 4 3 3 8 6 5 9 4 2 1 8 9 5 0 7 2 6 1 1 0 6 7 |
Output: 4 3 3 8 6 5 9 4 2 1 5 0 7 2 6 1 |
Data Structure
id[] of length Nid[p] identifies the set containing element p\[ \{0,5,6\}\ \{1,2,7\}\ \{3,4,8,9\} \]
// 0 1 2 3 4 5 6 7 8 9 index
int [] id = {0,1,1,8,8,0,0,1,8,8};
// find(5) == 0
Q: How to implement find(p)?
Data Structure
id[] of length Nid[p] identifies the set containing element p\[ \{0,5,6\}\ \{1,2,7\}\ \{3,4,8,9\} \]
// 0 1 2 3 4 5 6 7 8 9 index
int [] id = {0,1,1,8,8,0,0,1,8,8};
// find(5) == 0
Q: How to implement find(p)?
A: Easy, just return id[p]
Data Structure
id[] of length Nid[p] identifies the set containing element p
\[ \{0,5,6\}\ \{1,2,7\}\ \{3,4,8,9\} \underset{\textrm{union}(6,1)}{\Rightarrow} \{0,1,2,5,6,7\}\ \{3,4,8,9\} \]// 0 1 2 3 4 5 6 7 8 9 index
int [] id = {0,1,1,8,8,0,0,1,8,8};
union(6,1);
// id = ??
Q: How to implement union(p,q)?
Data Structure
id[] of length Nid[p] identifies the set containing element p
\[ \{0,5,6\}\ \{1,2,7\}\ \{3,4,8,9\} \underset{\textrm{union}(6,1)}{\Rightarrow} \{0,1,2,5,6,7\}\ \{3,4,8,9\} \]// 0 1 2 3 4 5 6 7 8 9 index
int [] id = {0,1,1,8,8,0,0,1,8,8};
union(6,1);
// id = ??
Q: How to implement union(p,q)?
A: Change all entries whose identifier equals id[p] to id[q].
id = {1,1,1,8,8,1,1,1,8,8}
public class QuickFindUF {
private int[] id;
public QuickFindUF(int N) {
// set id of each element to itself. N array accesses
id = new int[N];
for(int i = 0; i < N; i++)
id[i] = i;
}
public int find(int p) {
// return the id of p. 1 array access
return id[p];
}
public void union(int p, int q) {
// change all entries with id[p] to id[q]
// N+2 to 2N+2 array accesses
int pid = id[p];
int qid = id[q];
for(int i = 0; i < id.length; i++) {
if(id[i] == pid) id[i] = qid;
}
}
}
| algorithm | initialize | union | find |
|---|---|---|---|
| quick-find | \(N\) | \(N\) | \(1\) |
Note: ignoring leading constant
Union is too expensive! Processing a sequence of \(N\) union operations on \(N\) elements takes more than \(N^2\) (quadratic) array accesses.
Rough standard (for now)
Ex. Huge problem for quick-find
Quadratic algorithms don't scale with technology

Data Structure
parent[] of length N, where parent[i] is parent of i in tree\[ \{0\}\ \{1\}\ \{2,3,4,9\}\ \{5,6\}\ \{7\}\ \{8\} \]

// 0 1 2 3 4 5 6 7 8 9 index
int [] parent = {0,1,9,4,9,6,6,7,8,9};
// parent of 3 is 4, parent of 4 is 9, parent of 9 is 9
// root of 3 is 9
// parent and root of 5 is 6
Q: How to implement find(p)?
\[ \{0\}\ \{1\}\ \{2,3,4,9\}\ \{5,6\}\ \{7\}\ \{8\} \]

// 0 1 2 3 4 5 6 7 8 9 index
int [] parent = {0,1,9,4,9,6,6,7,8,9};
// parent of 3 is 4, parent of 4 is 9, parent of 9 is 9
// root of 3 is 9
// parent and root of 5 is 6
Q: How to implement find(p)?
A: Return root of tree containing p
\[ \ldots \{2,3,4,9\} \{5,6\} \ldots \Rightarrow \ldots \{2,3,4,5,6,9\} \ldots \]

// 0 1 2 3 4 5 6 7 8 9 index
int [] parent = {0,1,9,4,9,6,6,7,8,9};
union(3, 5)
// parent = ???
Q: How to implement union(p,q)?
\[ \ldots \{2,3,4,9\} \{5,6\} \ldots \Rightarrow \ldots \{2,3,4,5,6,9\} \ldots \]

// 0 1 2 3 4 5 6 7 8 9 index
int [] parent = {0,1,9,4,9,6,6,7,8,9};
union(3, 5)
// parent = ???
Q: How to implement union(p,q)?
A: Set parent of p's root to parent of q's root.
\[ \ldots \{2,3,4,9\} \{5,6\} \ldots \Rightarrow \ldots \{2,3,4,5,6,9\} \ldots \]

// 0 1 2 3 4 5 6 7 8 9 index
int [] parent = {0,1,9,4,9,6,6,7,8,9};
union(3, 5)
// 0 1 2 3 4 5 6 7 8 9 index
// parent = {0,1,9,4,9,6,6,7,8,6}
// ^ only one value changes!
union(4,3) union(3,8) union(6,5) union(9,4) union(2,1) isConnected(8,9) !isConnected(5,4) union(5,0) union(7,2) union(6,1) union(7,3)
public class QuickUnionUF {
private int[] parent;
public QuickUnionUF(int N) {
// set parent of each element to itself. N array accesses
parent = new int[N];
for(int i = 0; i < N; i++)
parent[i] = i;
}
public int find(int p) {
// chase parent pointers until root. depth of p array accesses
while(p != parent[p])
p = parent[p];
return p;
}
public void union(int p, int q) {
// change root of p to point to root of q
// depth of p and q array accesses
int i = find(p);
int j = find(q);
if(i == j) return; // already unioned
parent[i] = j;
}
}
| algorithm | initialize | union | find |
|---|---|---|---|
| quick-find | \(N\) | \(N\) | \(1\) |
| quick-union | \(N\) | \(N^\dagger\) | \(N\) |
\(\dagger\) includes cost of finding two roots
Note: analyzed quick-union for worst case
Weighted quick-union

Suppose that the parent[] array during weighted quick union is
// 0 1 2 3 4 5 6 7 8 9
int [] parent = {0,0,0,0,0,0,7,8,8,8};

Which parent[] entry changes during union(2,6)?
| A. | parent[0] |
| B. | parent[2] |
| C. | parent[6] |
| D. | parent[8] |
union(4,3) union(3,8) union(6,5) union(9,4) union(2,1) union(5,0) union(7,2) union(6,1) union(7,3)
|
quick-union ![]() |
weighted quick-union ![]() |
A larger example: 100 sites, 88 union() operations
Data structure: same as quick-union, but maintain extra array size[i] to count number of elements in the tree rooted at i, initially set to 1.
Find: identical to quick-union
Union: modify quick-union to:
size[] arrayint i = find(p);
int j = find(q);
if(i == j) return;
if(size[i] < size[j]) { parent[i] = j; size[j] += size[i]; }
else { parent[j] = i; size[i] += size[j]; }
Running time
pProposition: depth of any node \(\textsf{x}\) is at most \(\lg N\)
![]() |
\[N = 10\] \[\text{depth}(\textsf{x}) \leq \lg N \approx 3.32\] |
Note: in computer science, \(\lg\) means base-2 logarithm
Proposition: depth of any node \(\textsf{x}\) is at most \(\lg N\)
Proof: What causes the depth of element \(\textsf{x}\) to increase? Increase by 1 when root of tree \(T_1\) containing \(\textsf{x}\) is linked to root of tree \(T_2\).

| algorithm | initialize | union | find |
|---|---|---|---|
| quick-find | \(N\) | \(N\) | \(1\) |
| quick-union | \(N\) | \(N^\dagger\) | \(N\) |
| weighted QU | \(N\) | \(\lg N^\dagger\) | \(\lg N\) |
\(\dagger\) includes cost of finding two roots
Note: analyzed quick-union for worst case
Key point: weighted quick-union makes it possible to solve problems that could not otherwise be addressed.
| algorithm | worst-case time |
|---|---|
| quick-find | \(M N\) |
| quick-union | \(M N\) |
| weighted QU | \(N + M \log N\) |
| QU + path compression* | \(N + M \log N\) |
| weighted QU + path compression* | \(N + M \invackermann(N) \approx N+M\) |
Order of growth for \(M\) union-find ops on a set of \(N\) elements
Example: \(10^9\) unions and finds with \(10^9\) elements
bwlabel() function in image processingThe game of Hex is played on a diamond-shaped board of hexagons. Two players alternate turns by placing their colored stones (red/blue, white/black, etc.) on the board, attempting to make a connection between their respective opposite sides.

Q: How to determine if a player has won?
A: Model as a dynamic-connectivity problem and use union-find
![]() |
![]() |
![]() |
This is the scientific method
Mathematical analysis