Union-Find (or Disjoint Set Union, DSU) is a data structure that tracks elements partitioned into disjoint subsets, supporting rapid merging () and representative finding () operations.
Optimized with path compression and union by rank/size, it achieves near-constant amortized time complexity, $O(\alpha(n))$, making it efficient for Kruskal’s algorithm and dynamic connectivity. [1, 2, 3, 4]
Key Concepts and Operations
- Find(x): Determines the representative (root) of the set containing element .
- Union(x, y): Merges the sets containing elements and .
- MakeSet(x): Initializes a new set containing only element .
- Structure: Typically implemented as a forest of trees, where each node points to its parent. The root of a tree is its own parent and acts as the representative. [4, 5, 6, 7, 8]
Optimizations
- Path Compression: During a operation, makes every node on the path point directly to the root, flattening the tree.
- Union by Rank/Size: Always attaches the smaller tree under the root of the larger tree to keep the tree shallow. [2, 8, 9]
Applications
- Graph Algorithms: Finding connected components and Kruskal's algorithm for Minimum Spanning Trees (MST).
- Cycle Detection: Efficiently checking if adding an edge creates a cycle in an undirected graph.
- Dynamic Connectivity: Tracking network connections. [3, 10]
Complexity
- Space: $O(n)$ to store parents and ranks.
- Time: Almost $O(1)$ amortized per operation when using both optimizations ($O(\alpha(n))$, where $\alpha$ is the Inverse Ackermann function). [1, 2, 9, 11, 12]
code in c++ :
#include <vector>
#include <numeric>
#include <iostream>
class UnionFind {
private:
std::vector<int> parent;
std::vector<int> rank; // Or size, to keep track of tree height/size
public:
// Constructor: Initializes each element as its own set with rank 0
UnionFind(int size) {
parent.resize(size);
rank.resize(size, 0); // Initialize ranks to 0
std::iota(parent.begin(), parent.end(), 0); // Fill parent array with 0, 1, ..., size-1
}
// Find operation with path compression
int find(int i) {
if (parent[i] == i)
return i;
// Path compression: set the parent of i to its ultimate root
return parent[i] = find(parent[i]);
}
// Union operation with union by rank
void unite(int i, int j) {
int root_i = find(i);
int root_j = find(j);
if (root_i != root_j) {
// Union by rank: attach the smaller rank tree under the larger rank tree
if (rank[root_i] < rank[root_j]) {
parent[root_i] = root_j;
} else if (rank[root_i] > rank[root_j]) {
parent[root_j] = root_i;
} else {
// If ranks are the same, attach one to the other and increment the rank of the new root
parent[root_j] = root_i;
rank[root_i]++;
}
}
}
// Helper function to check if two elements are in the same set
bool connected(int i, int j) {
return find(i) == find(j);
}
};
int main() {
// Example usage
UnionFind uf(5); // Create a UnionFind for 5 elements (0 to 4)
uf.unite(0, 1);
uf.unite(1, 2);
uf.unite(3, 4);
std::cout << "Are 0 and 2 connected? " << (uf.connected(0, 2) ? "Yes" : "No") << std::endl; // Yes
std::cout << "Are 0 and 4 connected? " << (uf.connected(0, 4) ? "Yes" : "No") << std::endl; // No
uf.unite(0, 4);
std::cout << "After union, are 0 and 4 connected? " << (uf.connected(0, 4) ? "Yes" : "No") << std::endl; // Yes
return 0;
}
References:
Comments
Post a Comment