Disjoint Set Union (Union-Find)
Disjoint Set Union (DSU)
1. The Big Idea — "Are These Two Things Connected?"
DSU (also called Union-Find) is the simplest data structure on Earth and yet one of the
most powerful. It maintains a partition of n elements into disjoint groups and answers two questions
blazingly fast:
find(x)— to which group doesxbelong?union(x, y)— merge the groups ofxandy.
n-টি element-কে কয়েকটি disjoint দলে ভাগ করে রাখে এবং দুটি প্রশ্নের অবিশ্বাস্য দ্রুত উত্তর দেয়: (১) find(x) — x কোন দলে আছে? (২) union(x, y) — x ও y-এর দল দুটিকে এক করে দাও।
With union-by-rank + path compression, every operation runs in inverse Ackermann time α(n) — a function so slow-growing that for any n ≤ 10600, α(n) ≤ 5.
2. Each Group Is a Tree
We store each group as a rooted tree; the root's name is the "group ID". Every element points to its
parent, and the root points to itself. find(x) walks up parent pointers; union(x, y)
attaches one root under the other.
find(x) root পর্যন্ত উঠে যায়; union(x, y) দুটি গাছের root-কে একটিকে অন্যটির নিচে জুড়ে দেয়।
3. Two Heuristics That Change Everything
| Heuristic | What it does | Effect |
|---|---|---|
| Union by rank/size | Always attach the shorter (smaller) tree under the taller (larger) one. | Tree height stays O(log n). |
| Path compression | During find, repoint every visited node directly to the root. | Amortizes future finds to O(α). |
| Both together | — | Each operation is essentially O(α(n)). |
Tarjan-এর প্রমাণ: m-টি operation ও n-টি element-এর মোট সময় O(m·α(n))। α(n) এত ধীরে বাড়ে যে দৃশ্যমান মহাবিশ্বে ফিট হওয়া যেকোনো n-এর জন্য α(n) ≤ ৫।
4. Live Code — DSU with Rank + Path Compression
#include <bits/stdc++.h>
using namespace std;
struct DSU {
vector<int> par, rnk;
int components;
DSU(int n) : par(n), rnk(n, 0), components(n) {
iota(par.begin(), par.end(), 0); // par[i] = i
}
int find(int x) {
while (par[x] != x) {
par[x] = par[par[x]]; // path compression (halving)
x = par[x];
}
return x;
}
bool unite(int x, int y) {
x = find(x); y = find(y);
if (x == y) return false;
if (rnk[x] < rnk[y]) swap(x, y);
par[y] = x;
if (rnk[x] == rnk[y]) ++rnk[x];
--components;
return true;
}
bool connected(int x, int y) { return find(x) == find(y); }
};
int main() {
DSU d(7); // 7 cities: 0..6
d.unite(0, 1); // Dhaka — Gazipur
d.unite(1, 2); // Gazipur — Narsingdi
d.unite(3, 4); // Chittagong — Cox's Bazar
cout << "0 & 2 connected? " << d.connected(0, 2) << "\n";
cout << "0 & 3 connected? " << d.connected(0, 3) << "\n";
cout << "Components = " << d.components << "\n";
d.unite(2, 4); // link the two regions
cout << "After uniting 2 & 4 → components = " << d.components << "\n";
return 0;
}
par[i] array-তে আমরা প্রতিটি element-এর parent রাখি। শুরুতে সবাই নিজেই নিজের parent। find-এ path halving ব্যবহার করেছি (একটি লাইনে: par[x] = par[par[x]]) — এটি classic full compression-এর মতই দ্রুত কিন্তু লেখা সহজ।
5. Live Code — Connected Components of an Undirected Graph
For every edge, call unite(u, v). After processing all edges, the number of distinct roots
equals the number of connected components.
#include <bits/stdc++.h>
using namespace std;
struct DSU {
vector<int> p, r;
DSU(int n) : p(n), r(n, 0) { iota(p.begin(), p.end(), 0); }
int find(int x) { return p[x] == x ? x : p[x] = find(p[x]); }
bool unite(int a, int b) {
a = find(a); b = find(b);
if (a == b) return false;
if (r[a] < r[b]) swap(a, b);
p[b] = a;
if (r[a] == r[b]) ++r[a];
return true;
}
};
int main() {
int n = 8;
vector<pair<int,int>> edges = {{0,1},{1,2},{3,4},{5,6},{6,7}};
DSU d(n);
for (auto& [u, v] : edges) d.unite(u, v);
int comps = 0;
for (int i = 0; i < n; ++i) if (d.find(i) == i) ++comps;
cout << "Connected components = " << comps << "\n";
return 0;
}
6. Practice Problems
-
Number of Provinces — given an n×n adjacency matrix, count connected groups.n×n adjacency matrix থেকে connected group-এর সংখ্যা বের করুন।
Show Answer
provinces.cpp#include <bits/stdc++.h> using namespace std; int p[200]; int f(int x) { return p[x] == x ? x : p[x] = f(p[x]); } int main() { vector<vector<int>> M = {{1,1,0},{1,1,0},{0,0,1}}; int n = M.size(); iota(p, p + n, 0); for (int i = 0; i < n; ++i) for (int j = i + 1; j < n; ++j) if (M[i][j]) p[f(i)] = f(j); int ans = 0; for (int i = 0; i < n; ++i) if (f(i) == i) ++ans; cout << ans << "\n"; } -
Redundant Connection — given an undirected graph that is a tree plus one extra edge, find the extra edge.একটি tree-এর সাথে অতিরিক্ত একটি edge যোগ করা আছে — সেই অতিরিক্ত edge-টি বের করুন।
Show Answer
redundant.cpp#include <bits/stdc++.h> using namespace std; int p[1010]; int f(int x) { return p[x] == x ? x : p[x] = f(p[x]); } int main() { vector<pair<int,int>> e = {{1,2},{2,3},{3,4},{1,4},{1,5}}; iota(p, p + 1010, 0); for (auto& [u, v] : e) { if (f(u) == f(v)) { cout << u << " " << v << "\n"; return 0; } p[f(u)] = f(v); } } -
Accounts Merge — merge accounts that share at least one email; output unified email list per person.যেসব account-এ অন্তত একটি email common, সেগুলো merge করুন এবং প্রতি ব্যক্তির জন্য combined email list বের করুন।
Show Answer
accounts.cpp#include <bits/stdc++.h> using namespace std; int p[5000]; int f(int x){return p[x]==x?x:p[x]=f(p[x]);} int main() { vector<vector<string>> A = { {"Arif","a@x","a@y"}, {"Arif","a@y","a@z"}, {"Mim","m@x"} }; iota(p, p + 5000, 0); unordered_map<string,int> idx; unordered_map<string,string> owner; for (int i = 0; i < (int)A.size(); ++i) { for (int j = 1; j < (int)A[i].size(); ++j) { if (!idx.count(A[i][j])) { idx[A[i][j]] = idx.size(); owner[A[i][j]] = A[i][0]; } p[f(idx[A[i][1]])] = f(idx[A[i][j]]); } } map<int, set<string>> g; for (auto& [e, i] : idx) g[f(i)].insert(e); for (auto& [k, s] : g) { cout << owner[*s.begin()] << ": "; for (auto& e : s) cout << e << " "; cout << "\n"; } } -
Minimum Cost to Connect Cities — Kruskal's MST using DSU.শহরগুলো connect করার সর্বনিম্ন খরচ — Kruskal MST।
Show Answer
kruskal.cpp#include <bits/stdc++.h> using namespace std; int p[100]; int f(int x){return p[x]==x?x:p[x]=f(p[x]);} int main() { int n = 4; vector<tuple<int,int,int>> e = {{1,0,1},{4,0,2},{2,1,2},{3,1,3},{5,2,3}}; sort(e.begin(), e.end()); iota(p, p + n, 0); int cost = 0; for (auto& [w, u, v] : e) { if (f(u) != f(v)) { p[f(u)] = f(v); cost += w; } } cout << "MST cost = " << cost << "\n"; } -
Smallest Equivalent String — given pairs of equivalent characters, output the lexicographically smallest version of a string.কিছু character-জোড়া equivalent দেওয়া আছে; একটি string-এর lexicographically smallest version বের করুন।
Show Answer
eqstr.cpp#include <bits/stdc++.h> using namespace std; int p[26]; int f(int x){return p[x]==x?x:p[x]=f(p[x]);} void u(int a,int b){a=f(a);b=f(b);if(a==b)return;if(a<b)p[b]=a;else p[a]=b;} int main() { string s1 = "parker", s2 = "morris", base = "parser"; iota(p, p + 26, 0); for (int i = 0; i < (int)s1.size(); ++i) u(s1[i] - 'a', s2[i] - 'a'); for (char& c : base) c = f(c - 'a') + 'a'; cout << base << "\n"; } -
Satisfiability of Equality Equations — given equations like "a==b" and "b!=c", say whether all are satisfiable."a==b" ও "b!=c" এর মতো সমীকরণ দেওয়া আছে — সবগুলো একসাথে সঙ্গতিপূর্ণ কিনা বলুন।
Show Answer
equations.cpp#include <bits/stdc++.h> using namespace std; int p[26]; int f(int x){return p[x]==x?x:p[x]=f(p[x]);} int main() { vector<string> E = {"a==b","b==c","a!=c"}; iota(p, p + 26, 0); for (auto& e : E) if (e[1] == '=') p[f(e[0] - 'a')] = f(e[3] - 'a'); for (auto& e : E) if (e[1] == '!' && f(e[0] - 'a') == f(e[3] - 'a')) { cout << "unsatisfiable\n"; return 0; } cout << "satisfiable\n"; } -
Number of Islands II — given a grid and a stream of "add land" operations, output the island count after each.গ্রিডে একটির পর একটি ভূমি যোগ হচ্ছে — প্রতিবার যোগের পর island-এর সংখ্যা প্রিন্ট করুন।
Show Answer
islands2.cpp#include <bits/stdc++.h> using namespace std; int p[10000]; int f(int x){return p[x]==x?x:p[x]=f(p[x]);} int main() { int R = 3, C = 3; vector<int> land(R*C, 0); iota(p, p + R*C, 0); vector<pair<int,int>> ops = {{0,0},{0,1},{1,2},{2,1}}; int cnt = 0, dr[] = {-1,1,0,0}, dc[] = {0,0,-1,1}; for (auto& [r, c] : ops) { int id = r*C + c; if (land[id]) { cout << cnt << " "; continue; } land[id] = 1; ++cnt; for (int k = 0; k < 4; ++k) { int nr = r + dr[k], nc = c + dc[k]; if (nr < 0 || nr >= R || nc < 0 || nc >= C) continue; int nid = nr*C + nc; if (land[nid] && f(id) != f(nid)) { p[f(id)] = f(nid); --cnt; } } cout << cnt << " "; } cout << "\n"; }
Summary — Module 22
DSU stores each set as a tree, exposes find and unite, and with the two heuristics
(union-by-rank + path compression) every operation runs in nearly constant time. It is the secret weapon behind
Kruskal's MST, dynamic connectivity, and dozens of offline graph problems.