Skip to content

Commit 23e9dc0

Browse files
Add DisjointSet algorithm (TheAlgorithms#127)
* feat: add disjoint set algorithm * test: add tests for disjoint set * chore: Fix comments * doc: improve complexity documentation * Explain what alpha is * Fix typos, undo previous oops --------- Co-authored-by: Lars Müller <[email protected]>
1 parent 2603b22 commit 23e9dc0

File tree

2 files changed

+95
-0
lines changed

2 files changed

+95
-0
lines changed
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/**
2+
* A Disjoint Set is a data structure that keeps track of a set of elements
3+
* partitioned into a number of disjoint (non-overlapping) subsets.
4+
* Elements are uniquely represented by an index (0-based).
5+
*
6+
* The find operation uses path compression.
7+
* This allows the time complexity of the find operation be O(alpha(n)).
8+
* alpha(n) being the inverse Ackermann function.
9+
*
10+
* The join operation uses union by size: The smaller set is joined to the bigger one.
11+
*
12+
* You can perform the following operations on the disjoint set:
13+
* - find: Determine which subset a particular element is in - O(alpha(n))
14+
* - join: Join two subsets into a single subset - O(1)
15+
* - isSame: Check if two elements are in the same subset - O(1)
16+
*/
17+
export class DisjointSet {
18+
/** Direct parent for an element */
19+
private head: number[];
20+
21+
/** Size of the subtree above an element */
22+
private size: number[];
23+
24+
constructor(n: number) {
25+
// Initially each set has its own id element
26+
this.head = Array.from({ length: n }, (_, index) => index);
27+
this.size = Array(n).fill(1);
28+
}
29+
30+
/**
31+
* Find the representative index for an element
32+
*/
33+
find(index: number): number {
34+
if (this.head[index] != index) {
35+
// Use path compression (set an edge between the element and its head)
36+
this.head[index] = this.find(this.head[index]);
37+
}
38+
return this.head[index];
39+
}
40+
41+
42+
/**
43+
* Join two sets
44+
*/
45+
join(first: number, second: number): void {
46+
// Get the root of each set to join
47+
let firstHead = this.find(first);
48+
let secondHead = this.find(second);
49+
50+
// If they're the same (same set)
51+
if (firstHead === secondHead) return;
52+
53+
// Keep the bigger set in firstHead
54+
if (this.size[firstHead] < this.size[secondHead]) {
55+
[firstHead, secondHead] = [secondHead, firstHead];
56+
}
57+
58+
// Join the smallest set with the bigger one
59+
this.head[secondHead] = firstHead;
60+
61+
// Update size of the bigger set after join
62+
this.size[firstHead] += this.size[secondHead];
63+
}
64+
65+
/**
66+
* Check whether two elements are in the same set
67+
*/
68+
isSame(first: number, second: number): boolean {
69+
return this.find(first) === this.find(second);
70+
}
71+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { DisjointSet } from "../disjoint_set"
2+
3+
describe("DisjointSet", () => {
4+
let ds: DisjointSet;
5+
6+
beforeEach(() => {
7+
// Ensure create a new DisjoinSet instance on every test
8+
ds = new DisjointSet(10);
9+
});
10+
11+
it("should show proper head element after join", () => {
12+
expect(ds.find(0)).toEqual(0);
13+
14+
ds.join(1, 4);
15+
ds.join(2, 3);
16+
expect(ds.isSame(1, 4)).toEqual(true);
17+
expect(ds.isSame(2, 3)).toEqual(true);
18+
expect(ds.isSame(1, 3)).toEqual(false);
19+
20+
ds.join(4, 3);
21+
expect(ds.isSame(1, 3)).toEqual(true);
22+
expect(ds.isSame(2, 9)).toEqual(false);
23+
});
24+
})

0 commit comments

Comments
 (0)