Skip to content

Commit 1ae0c3c

Browse files
committed
feat: DisjointSet implementation
1 parent 2b5e701 commit 1ae0c3c

File tree

9 files changed

+409
-154
lines changed

9 files changed

+409
-154
lines changed

src/main/java/dataStructures/disjointSet/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,7 @@ a balanced tree and hence complexity does not necessarily improve
2626

2727
3. Weighted Union - Same idea of using a tree, but constructed in a way that the tree is balanced, leading to improved
2828
complexities. Can be further augmented with path compression.
29+
30+
## Notes
31+
Disjoint Set is a data structure designed to keep track of a set of elements partitioned into a number of
32+
non-overlapping subsets. It is not suited for handling duplicates and so our implementation ignores duplicates.
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
package dataStructures.disjointSet.quickFind;
2+
3+
import java.util.ArrayList;
4+
import java.util.HashMap;
5+
import java.util.List;
6+
import java.util.Map;
7+
8+
/**
9+
* Implementation of quick-find structure; Turns a list of objects into a data structure that supports union operations
10+
*
11+
* @param <T> generic type of object to be stored
12+
*/
13+
public class DisjointSet<T> {
14+
private final Map<T, Integer> identifier;
15+
16+
/**
17+
* Basic constructor to create the Disjoint Set data structure.
18+
*/
19+
public DisjointSet() {
20+
identifier = new HashMap<>();
21+
}
22+
23+
/**
24+
* Constructor to initialize Disjoint Set with a known list of objects.
25+
* @param objects
26+
*/
27+
public DisjointSet(List<T> objects) {
28+
identifier = new HashMap<>();
29+
int size = objects.size();
30+
for (int i = 0; i < size; i++) {
31+
// internally, component identity is tracked with integers
32+
identifier.put(objects.get(i), identifier.size()); // each obj initialize with a unique identity using size;
33+
}
34+
}
35+
36+
public int size() {
37+
return identifier.size();
38+
}
39+
40+
/**
41+
* Adds an object into the structure.
42+
* @param obj
43+
*/
44+
public void add(T obj) {
45+
identifier.put(obj, identifier.size());
46+
}
47+
48+
/**
49+
* Checks if object a and object b are in the same component.
50+
* @param a
51+
* @param b
52+
* @return a boolean value
53+
*/
54+
public boolean find(T a, T b) {
55+
if (!identifier.containsKey(a) || !identifier.containsKey(b)) { // key(s) does not even exist
56+
return false;
57+
}
58+
return identifier.get(a).equals(identifier.get(b));
59+
}
60+
61+
/**
62+
* Merge the components of object a and object b.
63+
* @param a
64+
* @param b
65+
*/
66+
public void union(T a, T b) {
67+
if (!identifier.containsKey(a) || !identifier.containsKey(b)) { // key(s) does not even exist; do nothing
68+
return;
69+
}
70+
71+
if (identifier.get(a).equals(identifier.get(b))) { // already same; do nothing
72+
return;
73+
}
74+
75+
int compOfA = identifier.get(a);
76+
int compOfB = identifier.get(b);
77+
for (T obj : identifier.keySet()) {
78+
if (identifier.get(obj).equals(compOfA)) {
79+
identifier.put(obj, compOfB);
80+
}
81+
}
82+
}
83+
84+
/**
85+
* Retrieves all elements that are in the same component as the specified object. Not a typical operation
86+
* but here to illustrate other use case.
87+
* @param a
88+
* @return a list of objects
89+
*/
90+
public List<T> retrieveFromSameComponent(T a) {
91+
List<T> ret = new ArrayList<>();
92+
for (T obj : identifier.keySet()) {
93+
if (find(a, obj)) {
94+
ret.add(obj);
95+
}
96+
}
97+
return ret;
98+
}
99+
}

src/main/java/dataStructures/disjointSet/quickFind/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22

33
## Background
44
Every object will be assigned a component identity. The implementation of Quick Find often involves
5-
an underlying array that tracks the component identity of each object.
5+
an underlying array or hash map that tracks the component identity of each object. Here, we use a hash map.
66

77
### Union
88
Between the two components, decide on the component d, to represent the combined set. Let the other
9-
component's identity be d'. Simply iterate over the component identifier array, and for any element with
9+
component's identity be d'. Simply iterate over the component identifier array / map, and for any element with
1010
identity d', assign it to d.
1111

1212
### Find

src/main/java/dataStructures/disjointSet/quickFind/generalised/QuickFind.java

Lines changed: 0 additions & 87 deletions
This file was deleted.

src/main/java/dataStructures/disjointSet/quickFind/simplified/QuickFind.java

Lines changed: 0 additions & 59 deletions
This file was deleted.
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
package dataStructures.disjointSet.weightedUnion;
2+
3+
import java.util.ArrayList;
4+
import java.util.List;
5+
import java.util.Map;
6+
import java.util.HashMap;
7+
8+
/**
9+
* Implementation of weighted-union structure;
10+
* Turns a list of objects into a data structure that supports union operations.
11+
* <p>
12+
* Note that implementation below includes path compression. Refer to README for more details
13+
*
14+
* @param <T> generic type of object to be stored
15+
*/
16+
public class DisjointSet<T> {
17+
private final Map<T, T> parents;
18+
private final Map<T, Integer> size;
19+
20+
/**
21+
* Basic constructor to initialize Disjoint Set structure using weighted union concept.
22+
*/
23+
public DisjointSet() {
24+
parents = new HashMap<>();
25+
size = new HashMap<>();
26+
}
27+
28+
/**
29+
* Constructor to initialize Disjoint Set structure with a known list of objects.
30+
* @param objects
31+
*/
32+
public DisjointSet(List<T> objects) {
33+
parents = new HashMap<>();
34+
size = new HashMap<>();
35+
for (int i = 0; i < objects.size(); i++) {
36+
T obj = objects.get(i);
37+
parents.put(obj, obj); // initially, every object forms a tree, with itself as the root
38+
size.put(obj, 1); // each tree has size 1 at the start
39+
}
40+
}
41+
42+
/**
43+
* Internal helper method to find the root (identifier) of an object. Note that path compression has been included.
44+
* A point of concern might be performing path compression would require updating the sizes tracked by each node
45+
* for consistency's sake. But doing so does not affect correctness of algorithm.
46+
* Because all the algorithm needs is the correct size of each subtree to decide on how to union
47+
* and shifting descendants around does not affect size of subtree.
48+
* @param obj
49+
* @return the root of the subtree.
50+
*/
51+
private T findRoot(T obj) {
52+
while (!obj.equals(parents.get(obj))) {
53+
T parent = parents.get(obj);
54+
// START OF PATH COMPRESSION
55+
T grandParent = parents.get(parent);
56+
parents.put(obj, grandParent); // powerful one-liner to reduce the height of trees every traversal
57+
// END
58+
obj = parent;
59+
}
60+
return obj;
61+
}
62+
63+
public int size() {
64+
return parents.size();
65+
}
66+
67+
/**
68+
* Adds an object into the structure.
69+
* @param obj
70+
*/
71+
public void add(T obj) {
72+
parents.put(obj, obj);
73+
size.put(obj, 1);
74+
}
75+
76+
/**
77+
* Checks if object a and object b are in the same component.
78+
* @param a
79+
* @param b
80+
* @return
81+
*/
82+
public boolean find(T a, T b) {
83+
T rootOfA = findRoot(a);
84+
T rootOfB = findRoot(b);
85+
return rootOfA.equals(rootOfB);
86+
}
87+
88+
/**
89+
* Merge the components of object a and object b.
90+
* @param a
91+
* @param b
92+
*/
93+
public void union(T a, T b) {
94+
T rootOfA = findRoot(a);
95+
T rootOfB = findRoot(b);
96+
int sizeA = size.get(rootOfA);
97+
int sizeB = size.get(rootOfB);
98+
99+
if (sizeA < sizeB) {
100+
parents.put(rootOfA, rootOfB); // update root A to be child of root B
101+
size.put(rootOfB, size.get(rootOfB) + size.get(rootOfA)); // update size of bigger tree
102+
} else {
103+
parents.put(rootOfB, rootOfA); // update root B to be child of root A
104+
size.put(rootOfA, size.get(rootOfA) + size.get(rootOfB)); // update size of bigger tree
105+
}
106+
}
107+
108+
/**
109+
* Retrieves all elements that are in the same component as the specified object. Not a typical operation
110+
* but here to illustrate other use case.
111+
* @param a
112+
* @return a list of objects
113+
*/
114+
public List<T> retrieveFromSameComponent(T a) {
115+
List<T> ret = new ArrayList<>();
116+
for (T obj : parents.keySet()) {
117+
if (find(a, obj)) {
118+
ret.add(obj);
119+
}
120+
}
121+
return ret;
122+
}
123+
}

0 commit comments

Comments
 (0)