use std::cmp::Ordering; use std::mem; use rustc_index::{Idx, IndexVec}; #[cfg(test)] mod tests; /// Simple implementation of a union-find data structure, i.e. a disjoint-set /// forest. #[derive(Debug)] pub struct UnionFind { table: IndexVec>, } #[derive(Debug)] struct UnionFindEntry { /// Transitively points towards the "root" of the set containing this key. /// /// Invariant: A root key is its own parent. parent: Key, /// When merging two "root" keys, their ranks determine which key becomes /// the new root, to prevent the parent tree from becoming unnecessarily /// tall. See [`UnionFind::unify`] for details. rank: u32, } impl UnionFind { /// Creates a new disjoint-set forest containing the keys `0..num_keys`. /// Initially, every key is part of its own one-element set. pub fn new(num_keys: usize) -> Self { // Initially, every key is the root of its own set, so its parent is itself. Self { table: IndexVec::from_fn_n(|key| UnionFindEntry { parent: key, rank: 0 }, num_keys) } } /// Returns the "root" key of the disjoint-set containing the given key. /// If two keys have the same root, they belong to the same set. /// /// Also updates internal data structures to make subsequent `find` /// operations faster. pub fn find(&mut self, key: Key) -> Key { // Loop until we find a key that is its own parent. let mut curr = key; while let parent = self.table[curr].parent && curr != parent { // Perform "path compression" by peeking one layer ahead, and // setting the current key's parent to that value. // (This works even when `parent` is the root of its set, because // of the invariant that a root is its own parent.) let parent_parent = self.table[parent].parent; self.table[curr].parent = parent_parent; // Advance by one step and continue. curr = parent; } curr } /// Merges the set containing `a` and the set containing `b` into one set. /// /// Returns the common root of both keys, after the merge. pub fn unify(&mut self, a: Key, b: Key) -> Key { let mut a = self.find(a); let mut b = self.find(b); // If both keys have the same root, they're already in the same set, // so there's nothing more to do. if a == b { return a; }; // Ensure that `a` has strictly greater rank, swapping if necessary. // If both keys have the same rank, increment the rank of `a` so that // future unifications will also prefer `a`, leading to flatter trees. match Ord::cmp(&self.table[a].rank, &self.table[b].rank) { Ordering::Less => mem::swap(&mut a, &mut b), Ordering::Equal => self.table[a].rank += 1, Ordering::Greater => {} } debug_assert!(self.table[a].rank > self.table[b].rank); debug_assert_eq!(self.table[b].parent, b); // Make `a` the parent of `b`. self.table[b].parent = a; a } /// Takes a "snapshot" of the current state of this disjoint-set forest, in /// the form of a vector that directly maps each key to its current root. pub fn snapshot(&mut self) -> IndexVec { self.table.indices().map(|key| self.find(key)).collect() } }