Skip to content

Commit 2e76caa

Browse files
committedMay 3, 2018
Add disjoint set.
1 parent de5b771 commit 2e76caa

File tree

6 files changed

+463
-0
lines changed

6 files changed

+463
-0
lines changed
 

‎README.md

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
* Segment Tree or Interval Tree
2121
* Binary Indexed Tree or Fenwick Tree
2222
9. [Graph](https://github.com/trekhleb/javascript-algorithms/tree/master/src/data-structures/graph) (both directed and undirected)
23+
9. [Disjoint Set](https://github.com/trekhleb/javascript-algorithms/tree/master/src/data-structures/disjoint-set)
2324

2425
## Algorithms
2526

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import DisjointSetItem from './DisjointSetItem';
2+
3+
export default class DisjointSet {
4+
/**
5+
* @param {function(value: *)} [keyCallback]
6+
*/
7+
constructor(keyCallback) {
8+
this.keyCallback = keyCallback;
9+
this.items = {};
10+
}
11+
12+
/**
13+
* @param {*} itemValue
14+
* @return {DisjointSet}
15+
*/
16+
makeSet(itemValue) {
17+
const disjointSetItem = new DisjointSetItem(itemValue, this.keyCallback);
18+
19+
if (!this.items[disjointSetItem.getKey()]) {
20+
// Add new item only in case if it not presented yet.
21+
this.items[disjointSetItem.getKey()] = disjointSetItem;
22+
}
23+
24+
return this;
25+
}
26+
27+
/**
28+
* @param {*} itemValue
29+
* @return {(string|null)}
30+
*/
31+
find(itemValue) {
32+
const templateDisjointItem = new DisjointSetItem(itemValue, this.keyCallback);
33+
34+
// Try to find item itself;
35+
const requiredDisjointItem = this.items[templateDisjointItem.getKey()];
36+
37+
if (!requiredDisjointItem) {
38+
return null;
39+
}
40+
41+
return requiredDisjointItem.getRoot().getKey();
42+
}
43+
44+
/**
45+
* @param {*} valueA
46+
* @param {*} valueB
47+
* @return {DisjointSet}
48+
*/
49+
union(valueA, valueB) {
50+
const rootKeyA = this.find(valueA);
51+
const rootKeyB = this.find(valueB);
52+
53+
if (rootKeyA === null || rootKeyB === null) {
54+
throw new Error('One or two values are not in sets');
55+
}
56+
57+
if (rootKeyA === rootKeyB) {
58+
// In case if both elements are already in the same set then just return its key.
59+
return this;
60+
}
61+
62+
const rootA = this.items[rootKeyA];
63+
const rootB = this.items[rootKeyB];
64+
65+
if (rootA.getAncestorsCount() < rootB.getAncestorsCount()) {
66+
// If rootB's tree is bigger then make rootB to be a new root.
67+
rootB.addChild(rootA);
68+
69+
return rootB.getKey();
70+
}
71+
72+
// If rootA's tree is bigger then make rootA to be a new root.
73+
rootA.addChild(rootB);
74+
75+
return this;
76+
}
77+
78+
/**
79+
* @param {*} valueA
80+
* @param {*} valueB
81+
* @return {boolean}
82+
*/
83+
inSameSet(valueA, valueB) {
84+
const rootKeyA = this.find(valueA);
85+
const rootKeyB = this.find(valueB);
86+
87+
if (rootKeyA === null || rootKeyB === null) {
88+
throw new Error('One or two values are not in sets');
89+
}
90+
91+
return rootKeyA === rootKeyB;
92+
}
93+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
export default class DisjointSetItem {
2+
/**
3+
* @param {*} value
4+
* @param {function(value: *)} [keyCallback]
5+
*/
6+
constructor(value, keyCallback) {
7+
this.value = value;
8+
this.keyCallback = keyCallback;
9+
/** @var {DisjointSetItem} this.parent */
10+
this.parent = null;
11+
this.children = {};
12+
}
13+
14+
/**
15+
* @return {*}
16+
*/
17+
getKey() {
18+
// Allow user to define custom key generator.
19+
if (this.keyCallback) {
20+
return this.keyCallback(this.value);
21+
}
22+
23+
// Otherwise use value as a key by default.
24+
return this.value;
25+
}
26+
27+
/**
28+
* @return {DisjointSetItem}
29+
*/
30+
getRoot() {
31+
return this.isRoot() ? this : this.parent.getRoot();
32+
}
33+
34+
/**
35+
* @return {boolean}
36+
*/
37+
isRoot() {
38+
return this.parent === null;
39+
}
40+
41+
/**
42+
* @return {number}
43+
*/
44+
getAncestorsCount() {
45+
if (this.getChildren().length === 0) {
46+
return 0;
47+
}
48+
49+
let count = 0;
50+
51+
/** @var {DisjointSetItem} child */
52+
this.getChildren().forEach((child) => {
53+
// Count child itself.
54+
count += 1;
55+
56+
// Also add all children of current child.
57+
count += child.getAncestorsCount();
58+
});
59+
60+
return count;
61+
}
62+
63+
/**
64+
* @return {DisjointSetItem[]}
65+
*/
66+
getChildren() {
67+
return Object.values(this.children);
68+
}
69+
70+
/**
71+
* @param {DisjointSetItem} parentItem
72+
* @param {boolean} forceSettingParentChild
73+
* @return {DisjointSetItem}
74+
*/
75+
setParent(parentItem, forceSettingParentChild = true) {
76+
this.parent = parentItem;
77+
if (forceSettingParentChild) {
78+
parentItem.addChild(this);
79+
}
80+
81+
return this;
82+
}
83+
84+
/**
85+
* @param {DisjointSetItem} childItem
86+
* @return {DisjointSetItem}
87+
*/
88+
addChild(childItem) {
89+
this.children[childItem.getKey()] = childItem;
90+
childItem.setParent(this, false);
91+
92+
return this;
93+
}
94+
}
+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Disjoint Set
2+
3+
**Disjoint-set** data structure (also called a union–find data structure or merge–find set) is a data
4+
structure that tracks a set of elements partitioned into a number of disjoint (non-overlapping) subsets.
5+
It provides near-constant-time operations (bounded by the inverse Ackermann function) to *add new sets*,
6+
to *merge existing sets*, and to *determine whether elements are in the same set*.
7+
In addition to many other uses (see the Applications section), disjoint-sets play a key role in Kruskal's algorithm for finding the minimum spanning tree of a graph.
8+
9+
![disjoint set](https://upload.wikimedia.org/wikipedia/commons/6/67/Dsu_disjoint_sets_init.svg)
10+
11+
*MakeSet* creates 8 singletons.
12+
13+
![disjoint set](https://upload.wikimedia.org/wikipedia/commons/a/ac/Dsu_disjoint_sets_final.svg)
14+
15+
After some operations of *Union*, some sets are grouped together.
16+
17+
## References
18+
19+
- [Wikipedia](https://en.wikipedia.org/wiki/Disjoint-set_data_structure)
20+
- [By Abdul Bari on YouTube](https://www.youtube.com/watch?v=wU6udHRIkcc)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
import DisjointSet from '../DisjointSet';
2+
3+
describe('DisjointSet', () => {
4+
it('should throw error when trying to union and check not existing sets', () => {
5+
function mergeNotExistingSets() {
6+
const disjointSet = new DisjointSet();
7+
8+
disjointSet.union('A', 'B');
9+
}
10+
11+
function checkNotExistingSets() {
12+
const disjointSet = new DisjointSet();
13+
14+
disjointSet.inSameSet('A', 'B');
15+
}
16+
17+
expect(mergeNotExistingSets).toThrow();
18+
expect(checkNotExistingSets).toThrow();
19+
});
20+
21+
it('should do basic manipulations on disjoint set', () => {
22+
const disjointSet = new DisjointSet();
23+
24+
expect(disjointSet.find('A')).toBeNull();
25+
expect(disjointSet.find('B')).toBeNull();
26+
27+
disjointSet.makeSet('A');
28+
29+
expect(disjointSet.find('A')).toBe('A');
30+
expect(disjointSet.find('B')).toBeNull();
31+
32+
disjointSet.makeSet('B');
33+
34+
expect(disjointSet.find('A')).toBe('A');
35+
expect(disjointSet.find('B')).toBe('B');
36+
37+
disjointSet.makeSet('C');
38+
39+
expect(disjointSet.inSameSet('A', 'B')).toBeFalsy();
40+
41+
disjointSet.union('A', 'B');
42+
43+
expect(disjointSet.find('A')).toBe('A');
44+
expect(disjointSet.find('B')).toBe('A');
45+
expect(disjointSet.inSameSet('A', 'B')).toBeTruthy();
46+
expect(disjointSet.inSameSet('B', 'A')).toBeTruthy();
47+
expect(disjointSet.inSameSet('A', 'C')).toBeFalsy();
48+
49+
disjointSet.union('A', 'A');
50+
51+
disjointSet.union('B', 'C');
52+
53+
expect(disjointSet.find('A')).toBe('A');
54+
expect(disjointSet.find('B')).toBe('A');
55+
expect(disjointSet.find('C')).toBe('A');
56+
57+
expect(disjointSet.inSameSet('A', 'B')).toBeTruthy();
58+
expect(disjointSet.inSameSet('B', 'C')).toBeTruthy();
59+
expect(disjointSet.inSameSet('A', 'C')).toBeTruthy();
60+
61+
disjointSet
62+
.makeSet('E')
63+
.makeSet('F')
64+
.makeSet('G')
65+
.makeSet('H')
66+
.makeSet('I');
67+
68+
disjointSet
69+
.union('E', 'F')
70+
.union('F', 'G')
71+
.union('G', 'H')
72+
.union('H', 'I');
73+
74+
expect(disjointSet.inSameSet('A', 'I')).toBeFalsy();
75+
expect(disjointSet.inSameSet('E', 'I')).toBeTruthy();
76+
77+
disjointSet.union('I', 'C');
78+
79+
expect(disjointSet.find('I')).toBe('E');
80+
expect(disjointSet.inSameSet('A', 'I')).toBeTruthy();
81+
});
82+
83+
it('should union smaller set with bigger one making bigger one to be new root', () => {
84+
const disjointSet = new DisjointSet();
85+
86+
disjointSet
87+
.makeSet('A')
88+
.makeSet('B')
89+
.makeSet('C')
90+
.union('B', 'C')
91+
.union('A', 'C');
92+
93+
expect(disjointSet.find('A')).toBe('B');
94+
});
95+
96+
it('should do basic manipulations on disjoint set with custom key extractor', () => {
97+
const keyExtractor = value => value.key;
98+
99+
const disjointSet = new DisjointSet(keyExtractor);
100+
101+
const itemA = { key: 'A', value: 1 };
102+
const itemB = { key: 'B', value: 2 };
103+
const itemC = { key: 'C', value: 3 };
104+
105+
expect(disjointSet.find(itemA)).toBeNull();
106+
expect(disjointSet.find(itemB)).toBeNull();
107+
108+
disjointSet.makeSet(itemA);
109+
110+
expect(disjointSet.find(itemA)).toBe('A');
111+
expect(disjointSet.find(itemB)).toBeNull();
112+
113+
disjointSet.makeSet(itemB);
114+
115+
expect(disjointSet.find(itemA)).toBe('A');
116+
expect(disjointSet.find(itemB)).toBe('B');
117+
118+
disjointSet.makeSet(itemC);
119+
120+
expect(disjointSet.inSameSet(itemA, itemB)).toBeFalsy();
121+
122+
disjointSet.union(itemA, itemB);
123+
124+
expect(disjointSet.find(itemA)).toBe('A');
125+
expect(disjointSet.find(itemB)).toBe('A');
126+
expect(disjointSet.inSameSet(itemA, itemB)).toBeTruthy();
127+
expect(disjointSet.inSameSet(itemB, itemA)).toBeTruthy();
128+
expect(disjointSet.inSameSet(itemA, itemC)).toBeFalsy();
129+
130+
disjointSet.union(itemA, itemC);
131+
132+
expect(disjointSet.find(itemA)).toBe('A');
133+
expect(disjointSet.find(itemB)).toBe('A');
134+
expect(disjointSet.find(itemC)).toBe('A');
135+
136+
expect(disjointSet.inSameSet(itemA, itemB)).toBeTruthy();
137+
expect(disjointSet.inSameSet(itemB, itemC)).toBeTruthy();
138+
expect(disjointSet.inSameSet(itemA, itemC)).toBeTruthy();
139+
});
140+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import DisjointSetItem from '../DisjointSetItem';
2+
3+
describe('DisjointSetItem', () => {
4+
it('should do basic manipulation with disjoint set item', () => {
5+
const itemA = new DisjointSetItem('A');
6+
const itemB = new DisjointSetItem('B');
7+
const itemC = new DisjointSetItem('C');
8+
const itemD = new DisjointSetItem('D');
9+
10+
expect(itemA.getAncestorsCount()).toBe(0);
11+
expect(itemA.getChildren()).toEqual([]);
12+
expect(itemA.getKey()).toBe('A');
13+
expect(itemA.getRoot()).toEqual(itemA);
14+
expect(itemA.isRoot()).toBeTruthy();
15+
expect(itemB.isRoot()).toBeTruthy();
16+
17+
itemA.addChild(itemB);
18+
itemD.setParent(itemC);
19+
20+
expect(itemA.getAncestorsCount()).toBe(1);
21+
expect(itemC.getAncestorsCount()).toBe(1);
22+
23+
expect(itemB.getAncestorsCount()).toBe(0);
24+
expect(itemD.getAncestorsCount()).toBe(0);
25+
26+
expect(itemA.getChildren().length).toBe(1);
27+
expect(itemC.getChildren().length).toBe(1);
28+
29+
expect(itemA.getChildren()[0]).toEqual(itemB);
30+
expect(itemC.getChildren()[0]).toEqual(itemD);
31+
32+
expect(itemB.getChildren().length).toBe(0);
33+
expect(itemD.getChildren().length).toBe(0);
34+
35+
expect(itemA.getRoot()).toEqual(itemA);
36+
expect(itemB.getRoot()).toEqual(itemA);
37+
38+
expect(itemC.getRoot()).toEqual(itemC);
39+
expect(itemD.getRoot()).toEqual(itemC);
40+
41+
expect(itemA.isRoot()).toBeTruthy();
42+
expect(itemB.isRoot()).toBeFalsy();
43+
expect(itemC.isRoot()).toBeTruthy();
44+
expect(itemD.isRoot()).toBeFalsy();
45+
46+
itemA.addChild(itemC);
47+
48+
expect(itemA.isRoot()).toBeTruthy();
49+
expect(itemB.isRoot()).toBeFalsy();
50+
expect(itemC.isRoot()).toBeFalsy();
51+
expect(itemD.isRoot()).toBeFalsy();
52+
53+
expect(itemA.getAncestorsCount()).toEqual(3);
54+
expect(itemB.getAncestorsCount()).toEqual(0);
55+
expect(itemC.getAncestorsCount()).toEqual(1);
56+
});
57+
58+
it('should do basic manipulation with disjoint set item with custom key extractor', () => {
59+
const keyExtractor = (value) => {
60+
return value.key;
61+
};
62+
63+
const itemA = new DisjointSetItem({ key: 'A', value: 1 }, keyExtractor);
64+
const itemB = new DisjointSetItem({ key: 'B', value: 2 }, keyExtractor);
65+
const itemC = new DisjointSetItem({ key: 'C', value: 3 }, keyExtractor);
66+
const itemD = new DisjointSetItem({ key: 'D', value: 4 }, keyExtractor);
67+
68+
expect(itemA.getAncestorsCount()).toBe(0);
69+
expect(itemA.getChildren()).toEqual([]);
70+
expect(itemA.getKey()).toBe('A');
71+
expect(itemA.getRoot()).toEqual(itemA);
72+
expect(itemA.isRoot()).toBeTruthy();
73+
expect(itemB.isRoot()).toBeTruthy();
74+
75+
itemA.addChild(itemB);
76+
itemD.setParent(itemC);
77+
78+
expect(itemA.getAncestorsCount()).toBe(1);
79+
expect(itemC.getAncestorsCount()).toBe(1);
80+
81+
expect(itemB.getAncestorsCount()).toBe(0);
82+
expect(itemD.getAncestorsCount()).toBe(0);
83+
84+
expect(itemA.getChildren().length).toBe(1);
85+
expect(itemC.getChildren().length).toBe(1);
86+
87+
expect(itemA.getChildren()[0]).toEqual(itemB);
88+
expect(itemC.getChildren()[0]).toEqual(itemD);
89+
90+
expect(itemB.getChildren().length).toBe(0);
91+
expect(itemD.getChildren().length).toBe(0);
92+
93+
expect(itemA.getRoot()).toEqual(itemA);
94+
expect(itemB.getRoot()).toEqual(itemA);
95+
96+
expect(itemC.getRoot()).toEqual(itemC);
97+
expect(itemD.getRoot()).toEqual(itemC);
98+
99+
expect(itemA.isRoot()).toBeTruthy();
100+
expect(itemB.isRoot()).toBeFalsy();
101+
expect(itemC.isRoot()).toBeTruthy();
102+
expect(itemD.isRoot()).toBeFalsy();
103+
104+
itemA.addChild(itemC);
105+
106+
expect(itemA.isRoot()).toBeTruthy();
107+
expect(itemB.isRoot()).toBeFalsy();
108+
expect(itemC.isRoot()).toBeFalsy();
109+
expect(itemD.isRoot()).toBeFalsy();
110+
111+
expect(itemA.getAncestorsCount()).toEqual(3);
112+
expect(itemB.getAncestorsCount()).toEqual(0);
113+
expect(itemC.getAncestorsCount()).toEqual(1);
114+
});
115+
});

0 commit comments

Comments
 (0)
Please sign in to comment.