Skip to content

Commit 24ddf08

Browse files
Create Disjoint Set in graph-collections crate
1 parent 6b7217c commit 24ddf08

3 files changed

Lines changed: 546 additions & 0 deletions

File tree

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
/// Union-Find (disjoint set union) with **union-by-rank**.
2+
///
3+
/// Supports O(log n) `find` and `union` in this phase. Elements are represented as integer indices `0..n`. Create a `DisjointSet` of size `n`, then call `union` to merge components and `find` to discover which component an element belongs to.
4+
///
5+
/// # Examples
6+
///
7+
/// ```
8+
/// use graph_collections::DisjointSet;
9+
///
10+
/// let mut ds = DisjointSet::new(5); // indices 0..5
11+
/// assert_eq!(ds.count(), 5);
12+
///
13+
/// ds.union(0, 1);
14+
/// ds.union(1, 2);
15+
/// assert!(ds.connected(0, 2));
16+
/// assert!(!ds.connected(0, 3));
17+
/// assert_eq!(ds.count(), 3); // {0,1,2}, {3}, {4}
18+
/// ```
19+
#[derive(Debug, Clone)]
20+
pub struct DisjointSet {
21+
parent: Vec<usize>,
22+
rank: Vec<usize>,
23+
count: usize, // number of disjoint sets
24+
}
25+
26+
impl DisjointSet {
27+
/// Creates a `DisjointSet` with `n` singleton sets, one per index `0..n`.
28+
///
29+
/// Each element is initially its own parent (root), and every rank starts
30+
/// at 0.
31+
///
32+
/// # Panics
33+
///
34+
/// Does not panic, but `n = 0` gives an empty structure where every
35+
/// operation on any index would panic at the index.
36+
///
37+
/// # Examples
38+
///
39+
/// ```
40+
/// use graph_collections::DisjointSet;
41+
///
42+
/// let ds = DisjointSet::new(4);
43+
/// assert_eq!(ds.count(), 4);
44+
/// ```
45+
pub fn new(n: usize) -> Self {
46+
Self {
47+
parent: (0..n).collect(),
48+
rank: vec![0; n],
49+
count: n,
50+
}
51+
}
52+
}
53+
54+
impl DisjointSet {
55+
/// Returns the **representative (root)** of the set containing `x`.
56+
///
57+
/// Uses iterative root-following without path compression. Two elements are in the same component iff `find(a) == find(b)`.
58+
///
59+
/// # Panics
60+
///
61+
/// Panics if `x >= n` (out of bounds).
62+
///
63+
/// # Examples
64+
///
65+
/// ```
66+
/// use graph_collections::DisjointSet;
67+
///
68+
/// let mut ds = DisjointSet::new(3);
69+
/// ds.union(0, 1);
70+
/// // Both 0 and 1 have the same root; 2 has its own.
71+
/// assert_eq!(ds.find(0), ds.find(1));
72+
/// assert_ne!(ds.find(0), ds.find(2));
73+
/// ```
74+
pub fn find(&mut self, mut x: usize) -> usize {
75+
// Walk to the root without modifying the tree.
76+
while self.parent[x] != x {
77+
x = self.parent[x];
78+
}
79+
x
80+
}
81+
82+
/// Merges the sets containing `x` and `y`.
83+
///
84+
/// Uses **union-by-rank**: the root with the lower rank is attached under
85+
/// the root with the higher rank, keeping trees shallow. When ranks are
86+
/// equal the second root is attached under the first and its rank increments.
87+
///
88+
/// Returns `true` if the two elements were in **different** sets (a merge
89+
/// actually happened), or `false` if they were already in the same set.
90+
///
91+
/// # Panics
92+
///
93+
/// Panics if `x >= n` or `y >= n`.
94+
///
95+
/// # Examples
96+
///
97+
/// ```
98+
/// use graph_collections::DisjointSet;
99+
///
100+
/// let mut ds = DisjointSet::new(4);
101+
/// assert!(ds.union(0, 1)); // new merge
102+
/// assert!(!ds.union(0, 1)); // already connected
103+
/// assert_eq!(ds.count(), 3);
104+
/// ```
105+
pub fn union(&mut self, x: usize, y: usize) -> bool {
106+
let rx = self.find(x);
107+
let ry = self.find(y);
108+
109+
if rx == ry {
110+
return false; // already in the same component
111+
}
112+
113+
// Attach smaller-rank tree under larger-rank tree.
114+
match self.rank[rx].cmp(&self.rank[ry]) {
115+
std::cmp::Ordering::Less => self.parent[rx] = ry,
116+
std::cmp::Ordering::Greater => self.parent[ry] = rx,
117+
std::cmp::Ordering::Equal => {
118+
self.parent[ry] = rx;
119+
self.rank[rx] += 1;
120+
}
121+
}
122+
123+
self.count -= 1;
124+
true
125+
}
126+
127+
/// Returns `true` if `x` and `y` belong to the same component.
128+
///
129+
/// Equivalent to `find(x) == find(y)`.
130+
///
131+
/// # Panics
132+
///
133+
/// Panics if `x >= n` or `y >= n`.
134+
///
135+
/// # Examples
136+
///
137+
/// ```
138+
/// use graph_collections::DisjointSet;
139+
///
140+
/// let mut ds = DisjointSet::new(5);
141+
/// ds.union(1, 3);
142+
/// assert!(ds.connected(1, 3));
143+
/// assert!(!ds.connected(1, 4));
144+
/// ```
145+
#[inline]
146+
pub fn connected(&mut self, x: usize, y: usize) -> bool {
147+
self.find(x) == self.find(y)
148+
}
149+
150+
/// Returns the number of **disjoint sets** (connected components).
151+
///
152+
/// Starts at `n` and decrements by one for each successful `union`.
153+
///
154+
/// # Examples
155+
///
156+
/// ```
157+
/// use graph_collections::DisjointSet;
158+
///
159+
/// let mut ds = DisjointSet::new(4);
160+
/// assert_eq!(ds.count(), 4);
161+
/// ds.union(0, 1);
162+
/// assert_eq!(ds.count(), 3);
163+
/// ds.union(2, 3);
164+
/// assert_eq!(ds.count(), 2);
165+
/// ds.union(0, 3);
166+
/// assert_eq!(ds.count(), 1);
167+
/// ```
168+
#[must_use]
169+
#[inline]
170+
pub fn count(&self) -> usize {
171+
self.count
172+
}
173+
}
174+
175+
impl DisjointSet {
176+
/// Returns the total number of elements (size passed to [`DisjointSet::new`]).
177+
///
178+
/// This is the **capacity** of the structure, not the number of components.
179+
/// For the number of components use [`count`](DisjointSet::count).
180+
///
181+
/// # Examples
182+
///
183+
/// ```
184+
/// use graph_collections::DisjointSet;
185+
///
186+
/// let ds = DisjointSet::new(10);
187+
/// assert_eq!(ds.size(), 10);
188+
/// ```
189+
#[must_use]
190+
#[inline]
191+
pub fn size(&self) -> usize {
192+
self.parent.len()
193+
}
194+
195+
/// Returns `true` if all elements belong to a single component.
196+
///
197+
/// # Examples
198+
///
199+
/// ```
200+
/// use graph_collections::DisjointSet;
201+
///
202+
/// let mut ds = DisjointSet::new(3);
203+
/// assert!(!ds.is_fully_connected());
204+
/// ds.union(0, 1);
205+
/// ds.union(1, 2);
206+
/// assert!(ds.is_fully_connected());
207+
/// ```
208+
#[inline]
209+
pub fn is_fully_connected(&self) -> bool {
210+
self.count == 1
211+
}
212+
}

crates/graph-collections/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@
55
//! Low-level collections for graph-rs: Stack, Queue, Heap, DisjointSet.
66
77
mod deque;
8+
mod disjoint_set;
89
mod min_heap;
910
mod priority_queue;
1011
mod queue;
1112
mod stack;
1213

1314
pub use deque::Deque;
15+
pub use disjoint_set::DisjointSet;
1416
pub use min_heap::MinHeap;
1517
pub use priority_queue::PriorityQueue;
1618
pub use queue::Queue;

0 commit comments

Comments
 (0)