-
Notifications
You must be signed in to change notification settings - Fork 6
/
disjointset.h
341 lines (290 loc) · 10.5 KB
/
disjointset.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
/*
* A disjoint set (or union find) data structure implementation
* See: https://en.wikipedia.org/wiki/Disjoint-set_data_structure
*
* Usage
* DisjointSet<int> ds(0); // Default, not-found element
* ds.add(5);
* ds.add(6);
* ds.add(7);
* ds.join(5, 6);
*
* int rep = ds.find(6);
*
* if (rep != ds.notfound())
* // 6 points to rep
*
* DisjointSet<int> ds2(std::move(ds));
*
* Construct: pass in element to be returned when an element isn't found, add
* will throw an error if you try adding this item
* Add: insert a new item if it doesn't already exist
* Join: join the two sets that these two items belong to, they don't have
* to be the representative elements
* Find: find the representative element of the set containing this item,
* returns this.notfound() if not found
*
* Notes
* This is meant to be used with small elements since it'll return one of
* them as the representative element. If you have large items you're
* storing, you'll probably want a different implementation that has some
* sort of separate index for the items.
*
* This requires that the elements be sortable. This is so that we can use
* std::set and its faster-than-linear-time find capabilities.
*
* This is primarily designed for use in a connected-component labeling
* algorithm using something like short (small images) or int (larger images)
* as the data type.
*/
#ifndef H_DISJOINTSET
#define H_DISJOINTSET
#include <set>
#include <iostream>
// Thrown if default element is added
class ElementIsDefault { };
// Thrown if there are internal errors
class DisjointItemWrongParent { };
class DisjointSetParentNull { };
class DisjointItemNotEmpty { };
class DisjointItemNotInParent { };
class DisjointSetItemNotFound { };
// Each item stored not only contains the element itself but a reference to the
// representative element, and if it is the representative element, a list to
// all those that point to it
template<class Type>
struct DisjointSetItem
{
typedef typename std::set<DisjointSetItem<Type>>::iterator set_iter;
typedef typename std::set<DisjointSetItem<Type>>::const_iterator set_const_iter;
typedef typename std::set<DisjointSetItem<Type>*>::iterator list_iter;
typedef typename std::set<DisjointSetItem<Type>*>::const_iterator list_const_iter;
// The item
Type value;
// The representative item for this set
DisjointSetItem<Type>* rep;
// If this is the representative item (rep == this), then we need to keep
// track of all the items in this set. Otherwise, this will be empty.
std::set<DisjointSetItem<Type>*> list;
// New item in new set, representative is itself
DisjointSetItem(Type value)
: value(value), rep(this)
{ }
// Allow moving, keep the rep pointing to this item if it previously was
DisjointSetItem(DisjointSetItem<Type>&&);
bool operator<(const DisjointSetItem<Type>& elem) const { return value < elem.value; }
bool operator>(const DisjointSetItem<Type>& elem) const { return value > elem.value; }
bool operator<=(const DisjointSetItem<Type>& elem) const { return value <= elem.value; }
bool operator>=(const DisjointSetItem<Type>& elem) const { return value >= elem.value; }
bool operator==(const DisjointSetItem<Type>& elem) const { return value == elem.value; }
bool operator!=(const DisjointSetItem<Type>& elem) const { return value != elem.value; }
private:
// Disallow copying (not yet implemented)
DisjointSetItem(const DisjointSetItem<Type>&);
DisjointSetItem<Type>& operator=(const DisjointSetItem<Type>&);
};
// The set of sets
template<class Type>
class DisjointSet
{
typedef typename std::set<DisjointSetItem<Type>>::iterator set_iter;
typedef typename std::set<DisjointSetItem<Type>>::const_iterator set_const_iter;
typedef typename std::set<DisjointSetItem<Type>*>::iterator list_iter;
typedef typename std::set<DisjointSetItem<Type>*>::const_iterator list_const_iter;
// The set of all sets
std::set<DisjointSetItem<Type>> sets;
// Default, not found item. Public but constant.
Type defaultElem;
public:
DisjointSet(Type notfound)
: defaultElem(notfound)
{ }
// Allow moving, just std::move everything
DisjointSet<Type>(DisjointSet<Type>&&);
DisjointSet<Type>& operator=(DisjointSet<Type>&&);
// Main operations
void add(Type elem);
void join(Type elem1, Type elem2);
Type find(Type elem) const;
Type notfound() const { return defaultElem; }
// Debugging
void selfcheck() const;
private:
// Disallow copying (not yet implemented)
DisjointSet<Type>(const DisjointSet<Type>&);
DisjointSet<Type>& operator=(const DisjointSet<Type>&);
template<class T>
friend std::ostream& operator<<(std::ostream&, const DisjointSet<T>&);
template<class T>
friend std::ostream& operator<<(std::ostream&, const DisjointSetItem<T>&);
};
// For debugging
template<class Type>
std::ostream& operator<<(std::ostream&, const DisjointSet<Type>&);
template<class Type>
std::ostream& operator<<(std::ostream&, const DisjointSetItem<Type>&);
//
// Implementation
//
template<class Type>
DisjointSetItem<Type>::DisjointSetItem(DisjointSetItem<Type>&& other)
: value(other.value), rep(other.rep), list(other.list)
{
// If the other points to itself, it is the representative for its set, thus
// point it to its new self. If it isn't, then keep pointing to the real
// representative and in theory the list should be empty.
if (other.rep == &other)
{
rep = this;
// Update all items in the list to point to the new self
for (DisjointSetItem<Type>* item : list)
{
item->rep = this;
}
}
// Otherwise, update the pointer in this one's parent
else
{
list_iter iter = rep->list.find(&other);
if (iter != rep->list.end())
{
// Can't change item in set, so just re-add it
rep->list.erase(iter);
rep->list.insert(this);
}
else
{
throw DisjointItemNotInParent();
}
}
}
template<class Type>
DisjointSet<Type>::DisjointSet(DisjointSet<Type>&& other)
: sets(std::move(other.sets)),
defaultElem(std::move(other.defaultElem))
{
}
template<class Type>
DisjointSet<Type>& DisjointSet<Type>::operator=(DisjointSet<Type>&& other)
{
sets = std::move(other.sets);
defaultElem = std::move(other.defaultElem);
return *this;
}
template<class Type>
void DisjointSet<Type>::add(Type elem)
{
if (elem == defaultElem)
throw ElementIsDefault();
// Insert if it doesn't already exist (existence based solely on the value
// of the item, not the representative or list)
sets.emplace(DisjointSetItem<Type>(elem));
}
template<class Type>
void DisjointSet<Type>::join(Type elem1, Type elem2)
{
set_iter rep1 = sets.find(DisjointSetItem<Type>(elem1));
set_iter rep2 = sets.find(DisjointSetItem<Type>(elem2));
// If one of them doesn't exist, then we don't have to join anything
if (rep1 == sets.end() || rep2 == sets.end())
return;
// If they both are part of the same list
if (rep1->rep == rep2->rep)
return;
// Determine which item is a part of a smaller list so we do less work
DisjointSetItem<Type>* small;
DisjointSetItem<Type>* large;
if (rep1->rep->list.size() > rep2->rep->list.size())
{
large = rep1->rep;
small = rep2->rep;
}
else
{
large = rep2->rep;
small = rep1->rep;
}
// Change all of the small item's representatives to that of the larger
// one and add the smaller items to the larger representative's list
for (DisjointSetItem<Type>* item : small->list)
{
item->rep = large;
large->list.insert(item);
}
// Finally, point the smaller representative to the representative of the
// larger set, add this to the larger one's list, and clear the smaller
// one's list
large->list.insert(small);
small->list.clear();
small->rep = large;
}
template<class Type>
Type DisjointSet<Type>::find(Type elem) const
{
// Search for this item by creating a new item. The representative doesn't
// matter since equality is checked solely based on the element itself.
DisjointSetItem<Type> item(elem);
set_const_iter iter = sets.find(DisjointSetItem<Type>(elem));
// Return representative element
if (iter != sets.end())
return iter->rep->value;
return defaultElem;
}
template<class Type>
void DisjointSet<Type>::selfcheck() const
{
for (const DisjointSetItem<Type>& item : sets)
{
if (item.rep == NULL)
{
throw DisjointSetParentNull();
}
else if (item.rep == &item)
{
for (const DisjointSetItem<Type>* ptr : item.list)
{
// This item points to the proper representative
if (ptr->rep != &item)
throw DisjointItemWrongParent();
// This item's list is empty
if (!ptr->list.empty())
throw DisjointItemNotEmpty();
}
}
else
{
// This item's list is empty
if (!item.list.empty())
throw DisjointItemNotEmpty();
// This item is in its representative's list. We must cast this
// since the list is of non-const pointers. However, we won't be
// modifying anything here so we're still treating it as const.
list_const_iter iter = item.rep->list.find(
const_cast<DisjointSetItem<Type>*>(&item));
if (iter == item.rep->list.end())
throw DisjointItemNotInParent();
}
// See if we can find this item
Type found = find(item.value);
if (found == defaultElem || found != item.rep->value)
throw DisjointSetItemNotFound();
}
}
template<class Type>
std::ostream& operator<<(std::ostream& os, const DisjointSet<Type>& set)
{
// Print out all the representative items
for (const DisjointSetItem<Type>& item : set.sets)
if (item.rep == &item)
os << item.value << " (" << item.rep->value << ") #"
<< item.rep->list.size() << ": " << item << std::endl;
return os;
}
template<class Type>
std::ostream& operator<<(std::ostream& os, const DisjointSetItem<Type>& elem)
{
for (DisjointSetItem<Type>* item : elem.list)
os << item->value << " (" << item->rep->value << "), ";
return os;
}
#endif