|
| 1 | +#pragma once |
| 2 | + |
| 3 | +#include <cstddef> |
| 4 | +#include <cstdint> |
| 5 | +#include <numeric> |
| 6 | +#include <utility> |
| 7 | +#include <vector> |
| 8 | + |
| 9 | +namespace bioimage_cpp::detail { |
| 10 | + |
| 11 | +// Disjoint-set / union-find with path compression and union-by-rank. |
| 12 | +// |
| 13 | +// Three merge variants are provided so both rank-driven and caller-driven |
| 14 | +// merge directions are expressible: |
| 15 | +// * merge(u, v): find both, then union by rank. |
| 16 | +// * merge_to(stable, removed): find both, then force `stable` to be the |
| 17 | +// parent regardless of rank. The caller picks |
| 18 | +// which root survives. |
| 19 | +// * unite_roots(a, b): preconditions: a and b are roots and a != b. Union |
| 20 | +// by rank without an additional find(). |
| 21 | +class UnionFind { |
| 22 | +public: |
| 23 | + explicit UnionFind(const std::size_t size) : parents_(size), ranks_(size, 0) { |
| 24 | + std::iota(parents_.begin(), parents_.end(), std::uint64_t{0}); |
| 25 | + } |
| 26 | + |
| 27 | + std::uint64_t find(const std::uint64_t node) { |
| 28 | + auto current = static_cast<std::size_t>(node); |
| 29 | + while (parents_[current] != current) { |
| 30 | + parents_[current] = parents_[parents_[current]]; |
| 31 | + current = static_cast<std::size_t>(parents_[current]); |
| 32 | + } |
| 33 | + return static_cast<std::uint64_t>(current); |
| 34 | + } |
| 35 | + |
| 36 | + std::uint64_t merge(std::uint64_t first, std::uint64_t second) { |
| 37 | + first = find(first); |
| 38 | + second = find(second); |
| 39 | + if (first == second) { |
| 40 | + return first; |
| 41 | + } |
| 42 | + return unite_roots(first, second); |
| 43 | + } |
| 44 | + |
| 45 | + std::uint64_t merge_to(std::uint64_t stable, std::uint64_t removed) { |
| 46 | + stable = find(stable); |
| 47 | + removed = find(removed); |
| 48 | + if (stable == removed) { |
| 49 | + return stable; |
| 50 | + } |
| 51 | + parents_[static_cast<std::size_t>(removed)] = stable; |
| 52 | + if (ranks_[static_cast<std::size_t>(stable)] <= ranks_[static_cast<std::size_t>(removed)]) { |
| 53 | + ranks_[static_cast<std::size_t>(stable)] = ranks_[static_cast<std::size_t>(removed)] + 1; |
| 54 | + } |
| 55 | + return stable; |
| 56 | + } |
| 57 | + |
| 58 | + std::uint64_t unite_roots(std::uint64_t first, std::uint64_t second) { |
| 59 | + if (ranks_[static_cast<std::size_t>(first)] < ranks_[static_cast<std::size_t>(second)]) { |
| 60 | + std::swap(first, second); |
| 61 | + } |
| 62 | + parents_[static_cast<std::size_t>(second)] = first; |
| 63 | + if (ranks_[static_cast<std::size_t>(first)] == ranks_[static_cast<std::size_t>(second)]) { |
| 64 | + ++ranks_[static_cast<std::size_t>(first)]; |
| 65 | + } |
| 66 | + return first; |
| 67 | + } |
| 68 | + |
| 69 | + [[nodiscard]] std::size_t size() const { |
| 70 | + return parents_.size(); |
| 71 | + } |
| 72 | + |
| 73 | +private: |
| 74 | + std::vector<std::uint64_t> parents_; |
| 75 | + std::vector<std::uint64_t> ranks_; |
| 76 | +}; |
| 77 | + |
| 78 | +} // namespace bioimage_cpp::detail |
0 commit comments