Flow
Documentation for the Flow C++ Library
Loading...
Searching...
No Matches
flow_disjoint_set.h
Go to the documentation of this file.
1#pragma once
2#include "flow_vector.h"
3
4namespace flow {
5
9 template <typename T, typename Allocator = PolymorphicAllocator<T>>
11 struct Node {
12 std::size_t groupId;
13 std::size_t rank;
15 };
16
17 public:
18 using value_type = T;
19 using reference = T&;
20 using const_reference = const T&;
21 using pointer = T*;
22 using const_pointer = const T*;
23 using iterator = T*;
24 using const_iterator = const T*;
25 using difference_type = std::ptrdiff_t;
26 using allocator_type = std::allocator_traits<Allocator>::template rebind_alloc<Node>;
28
29 private:
30 std::size_t groupSize_;
32
33 public:
34 constexpr DisjointSet()
35 noexcept(noexcept(container_type()))
36 : groupSize_(0), data_() {
37 }
38
39 explicit constexpr DisjointSet(const allocator_type& allocator)
40 noexcept(noexcept(container_type(allocator)))
41 : groupSize_(0), data_(allocator) {
42 }
43
44 constexpr DisjointSet(const DisjointSet&) = default;
45 constexpr DisjointSet(const DisjointSet& rhs, const allocator_type& allocator)
46 : groupSize_(rhs.groupSize_), data_(rhs.data_, allocator) {
47 }
48
49 constexpr DisjointSet(DisjointSet&&) = default;
50 constexpr DisjointSet(DisjointSet&& rhs, const allocator_type& allocator)
51 noexcept(noexcept(container_type(std::move(rhs.data_), allocator)))
52 : groupSize_(rhs.groupSize_), data_(std::move(rhs.data_), allocator) {
53 // rhs groupSize may not match up with the actual group size (0).
54 // That's fine, because we won't use that value after the move.
55 // Hopefully it won't come back and bite me in the future.
56 // Put a cat girl ascii here for good luck:
57 /*
58 ⣿⡟⠙⠛⠋⠩⠭⣉⡛⢛⠫⠭⠄⠒⠄⠄⠄⠈⠉⠛⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿
59 ⣿⡇⠄⠄⠄⠄⣠⠖⠋⣀⡤⠄⠒⠄⠄⠄⠄⠄⠄⠄⠄⠄⣈⡭⠭⠄⠄⠄⠉⠙
60 ⣿⡇⠄⠄⢀⣞⣡⠴⠚⠁⠄⠄⢀⠠⠄⠄⠄⠄⠄⠄⠄⠉⠄⠄⠄⠄⠄⠄⠄⠄
61 ⣿⡇⠄⡴⠁⡜⣵⢗⢀⠄⢠⡔⠁⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄
62 ⣿⡇⡜⠄⡜⠄⠄⠄⠉⣠⠋⠠⠄⢀⡄⠄⠄⣠⣆⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⢸
63 ⣿⠸⠄⡼⠄⠄⠄⠄⢰⠁⠄⠄⠄⠈⣀⣠⣬⣭⣛⠄⠁⠄⡄⠄⠄⠄⠄⠄⢀⣿
64 ⣏⠄⢀⠁⠄⠄⠄⠄⠇⢀⣠⣴⣶⣿⣿⣿⣿⣿⣿⡇⠄⠄⡇⠄⠄⠄⠄⢀⣾⣿
65 ⣿⣸⠈⠄⠄⠰⠾⠴⢾⣻⣿⣿⣿⣿⣿⣿⣿⣿⣿⢁⣾⢀⠁⠄⠄⠄⢠⢸⣿⣿
66 ⣿⣿⣆⠄⠆⠄⣦⣶⣦⣌⣿⣿⣿⣿⣷⣋⣀⣈⠙⠛⡛⠌⠄⠄⠄⠄⢸⢸⣿⣿
67 ⣿⣿⣿⠄⠄⠄⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠇⠈⠄⠄⠄⠄⠄⠈⢸⣿⣿
68 ⣿⣿⣿⠄⠄⠄⠘⣿⣿⣿⡆⢀⣈⣉⢉⣿⣿⣯⣄⡄⠄⠄⠄⠄⠄⠄⠄⠈⣿⣿
69 ⣿⣿⡟⡜⠄⠄⠄⠄⠙⠿⣿⣧⣽⣍⣾⣿⠿⠛⠁⠄⠄⠄⠄⠄⠄⠄⠄⠃⢿⣿
70 ⣿⡿⠰⠄⠄⠄⠄⠄⠄⠄⠄⠈⠉⠩⠔⠒⠉⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠐⠘⣿
71 ⣿⠃⠃⠄⠄⠄⠄⠄⠄⣀⢀⠄⠄⡀⡀⢀⣤⣴⣤⣤⣀⣀⠄⠄⠄⠄⠄⠄⠁⢹
72 */
73 }
74
79 template <std::forward_iterator It>
80 explicit constexpr DisjointSet(It first, It last, const allocator_type& allocator = {})
81 : groupSize_(std::distance(first, last)), data_(allocator) {
83 for (std::size_t i = 0; i < groupSize_; ++i) {
84 data_.pushBack(Node{ i, 0, *first });
85 ++first;
86 }
87 }
88
92 explicit constexpr DisjointSet(std::initializer_list<T> list, const allocator_type& allocator = {})
93 : DisjointSet(list.begin(), list.end(), allocator) {
94 }
95
96 ~DisjointSet() = default;
97
98 // Allocator.
99 allocator_type get_allocator() const noexcept {
100 return data_.get_allocator();
101 }
102
103 // Accessor.
104
107 constexpr std::size_t size() const {
108 return data_.size();
109 }
110
113 constexpr std::size_t groupSize() const {
114 return groupSize_;
115 }
116
119 constexpr bool empty() const {
120 return data_.empty();
121 }
122
125 void reserve(std::size_t n) {
126 data_.reserve(n);
127 }
128
131 constexpr void add(const T& value) {
132 data_.pushBack(Node{ data_.size(), 0, value });
133 ++groupSize_;
134 }
135
138 constexpr void add(T&& value) {
139 data_.pushBack(Node{ data_.size(), 0, std::move(value) });
140 ++groupSize_;
141 }
142
146 constexpr const T& operator[](std::size_t i) const {
147 return data_[i].value;
148 }
149
153 constexpr T& operator[](std::size_t i) {
154 return data_[i].value;
155 }
156
161 constexpr std::size_t find(std::size_t i) {
162 // Can switch to two passes using loop if overflows.
163 if (data_[i].groupId == i) {
164 return i;
165 }
166 return data_[i].groupId = find(data_[i].groupId);
167 }
168
173 constexpr bool isSameGroup(std::size_t i, std::size_t j) {
174 return find(i) == find(j);
175 }
176
182 constexpr bool merge(std::size_t i, std::size_t j) {
183 std::size_t p1 = find(i);
184 std::size_t p2 = find(j);
185 if (p1 == p2) {
186 return false;
187 }
188
189 if (data_[p1].rank <= data_[p2].rank) {
190 data_[p1].groupId = p2;
191 if (data_[p1].rank == data_[p2].rank) {
192 ++data_[p2].rank;
193 }
194 } else {
195 data_[p2].groupId = p1;
196 }
197
198 --groupSize_;
199 return true;
200 }
201 };
202}
A disjoint union set that uses path compression and union by rank.
constexpr T & operator[](std::size_t i)
Get the value of the i-th element.
constexpr std::size_t find(std::size_t i)
Find the group id of the i-th node. Perform path compression along the way.
constexpr bool isSameGroup(std::size_t i, std::size_t j)
Check if the i-th element and the j-th element are in the same group.
constexpr void add(const T &value)
Add an element to the disjoint set.
void reserve(std::size_t n)
Reserve enough memory for at least n elements.
std::allocator_traits< Allocator >::template rebind_alloc< Node > allocator_type
constexpr bool empty() const
Check if the disjoint set is empty.
constexpr DisjointSet(const allocator_type &allocator) noexcept(noexcept(container_type(allocator)))
constexpr DisjointSet() noexcept(noexcept(container_type()))
std::ptrdiff_t difference_type
constexpr DisjointSet(DisjointSet &&rhs, const allocator_type &allocator) noexcept(noexcept(container_type(std::move(rhs.data_), allocator)))
constexpr std::size_t groupSize() const
Return the number of group.
constexpr DisjointSet(const DisjointSet &)=default
constexpr void add(T &&value)
Add an element to the disjoint set.
~DisjointSet()=default
constexpr DisjointSet(It first, It last, const allocator_type &allocator={})
Iterator range constructor.
allocator_type get_allocator() const noexcept
constexpr const T & operator[](std::size_t i) const
Get the value of the i-th element.
constexpr DisjointSet(const DisjointSet &rhs, const allocator_type &allocator)
constexpr DisjointSet(DisjointSet &&)=default
constexpr std::size_t size() const
Return the number of input elements.
constexpr DisjointSet(std::initializer_list< T > list, const allocator_type &allocator={})
Initializer list constructor.
container_type data_
constexpr bool merge(std::size_t i, std::size_t j)
Merge the i-th element's group with the j-th element's group. Internally, it unions them by ranking.
Vector< Node, allocator_type > container_type
void pushBack(const T &value)
Adds an element to the end of the vector.
void reserve(std::size_t capacity)
Ensures the vector can hold at least the given capacity without reallocating.